Merge "Cleanup DM makefile to no longer depend on HWUI."
diff --git a/.clang-format b/.clang-format
index ff81590..90eedd8 100644
--- a/.clang-format
+++ b/.clang-format
@@ -70,8 +70,8 @@
 JavaScriptQuotes: Leave
 JavaScriptWrapImports: true
 KeepEmptyLinesAtTheStartOfBlocks: false
-MacroBlockBegin: ''
-MacroBlockEnd:   ''
+MacroBlockBegin: 'SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START'
+MacroBlockEnd:   'SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END'
 MaxEmptyLinesToKeep: 1
 NamespaceIndentation: None
 ObjCBlockIndentWidth: 2
diff --git a/Android.bp b/Android.bp
index 0af3593..fd10210 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19,7 +19,6 @@
         "include/effects/",
         "include/gpu/",
         "include/gpu/gl/",
-        "include/images/",
         "include/pathops/",
         "include/ports/",
         "include/svg/",
@@ -37,7 +36,6 @@
         "include/effects/",
         "include/gpu/",
         "include/gpu/gl/",
-        "include/images/",
         "include/pathops/",
         "include/ports/",
         "include/private/",
@@ -310,8 +308,8 @@
         "src/effects/Sk2DPathEffect.cpp",
         "src/effects/SkAlphaThresholdFilter.cpp",
         "src/effects/SkArcToPathEffect.cpp",
+        "src/effects/SkArithmeticImageFilter.cpp",
         "src/effects/SkArithmeticMode.cpp",
-        "src/effects/SkArithmeticMode_gpu.cpp",
         "src/effects/SkBlurDrawLooper.cpp",
         "src/effects/SkBlurMask.cpp",
         "src/effects/SkBlurMaskFilter.cpp",
@@ -376,6 +374,7 @@
         "src/gpu/GrContext.cpp",
         "src/gpu/GrCoordTransform.cpp",
         "src/gpu/GrDefaultGeoProcFactory.cpp",
+        "src/gpu/GrDistanceFieldGenFromVector.cpp",
         "src/gpu/GrDrawOpAtlas.cpp",
         "src/gpu/GrDrawOpTest.cpp",
         "src/gpu/GrDrawingManager.cpp",
@@ -407,7 +406,6 @@
         "src/gpu/GrProcessor.cpp",
         "src/gpu/GrProcessorUnitTest.cpp",
         "src/gpu/GrProgramDesc.cpp",
-        "src/gpu/GrProgramElement.cpp",
         "src/gpu/GrRectanizer_pow2.cpp",
         "src/gpu/GrRectanizer_skyline.cpp",
         "src/gpu/GrReducedClip.cpp",
diff --git a/BUILD.gn b/BUILD.gn
index 102b785..16eb634 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -25,8 +25,14 @@
   skia_use_piex = !is_win
   skia_use_zlib = true
 
+  skia_android_serial = ""
   skia_enable_android_framework_defines = false
+  skia_enable_discrete_gpu = true
   skia_enable_gpu = true
+  skia_enable_pdf = true
+  skia_enable_splicer =
+      is_skia_standalone && sanitize != "MSAN" &&
+      (is_linux || is_mac || is_win || (is_android && target_cpu == "arm64"))
   skia_enable_tools = is_skia_standalone
   skia_enable_vulkan_debug_layers = is_skia_standalone && is_debug
   skia_vulkan_sdk = getenv("VULKAN_SDK")
@@ -57,7 +63,6 @@
   "include/effects",
   "include/gpu",
   "include/gpu/gl",
-  "include/images",
   "include/pathops",
   "include/ports",
   "include/svg",
@@ -148,6 +153,9 @@
   if (skia_use_angle) {
     defines += [ "SK_ANGLE" ]
   }
+  if (skia_enable_discrete_gpu) {
+    defines += [ "SK_ENABLE_DISCRETE_GPU" ]
+  }
 }
 
 # Any code that's linked into Skia-the-library should use this config via += skia_library_configs.
@@ -425,7 +433,8 @@
 }
 
 optional("pdf") {
-  enabled = skia_use_zlib
+  enabled = skia_use_zlib && skia_enable_pdf
+  public_defines = [ "SK_SUPPORT_PDF" ]
 
   deps = [
     "//third_party/zlib",
@@ -435,7 +444,7 @@
 
   if (skia_use_sfntly) {
     deps += [ "//third_party/sfntly" ]
-    public_defines = [ "SK_PDF_USE_SFNTLY" ]
+    public_defines += [ "SK_PDF_USE_SFNTLY" ]
   }
 }
 
@@ -473,6 +482,15 @@
   ]
 }
 
+optional("splicer") {
+  enabled = skia_enable_splicer
+  public_defines = [ "SK_RASTER_PIPELINE_HAS_JIT" ]
+
+  sources = [
+    "src/splicer/SkSplicer.cpp",
+  ]
+}
+
 optional("typeface_freetype") {
   enabled = skia_use_freetype
 
@@ -533,6 +551,7 @@
     ":pdf",
     ":png",
     ":raw",
+    ":splicer",
     ":sse2",
     ":sse41",
     ":sse42",
@@ -758,6 +777,39 @@
     }
   }
 
+  template("test_app") {
+    if (defined(invoker.is_shared_library) && invoker.is_shared_library) {
+      shared_library("lib" + target_name) {
+        forward_variables_from(invoker, "*", [ "is_shared_library" ])
+        testonly = true
+      }
+    } else {
+      _executable = target_name
+      executable(_executable) {
+        forward_variables_from(invoker, "*", [ "is_shared_library" ])
+        testonly = true
+      }
+    }
+    if (is_android && skia_android_serial != "" && defined(_executable)) {
+      action("push_" + target_name) {
+        script = "gn/push_to_android.py"
+        deps = [
+          ":" + _executable,
+        ]
+        _stamp = "$target_gen_dir/$_executable.pushed_$skia_android_serial"
+        outputs = [
+          _stamp,
+        ]
+        args = [
+          rebase_path("$root_build_dir/$_executable"),
+          skia_android_serial,
+          rebase_path(_stamp),
+        ]
+        testonly = true
+      }
+    }
+  }
+
   test_lib("gpu_tool_utils") {
     public_include_dirs = []
     if (skia_enable_gpu) {
@@ -992,7 +1044,7 @@
       ]
     }
 
-    executable("lua_app") {
+    test_app("lua_app") {
       sources = [
         "tools/lua/lua_app.cpp",
       ]
@@ -1001,10 +1053,9 @@
         ":skia",
         "//third_party/lua",
       ]
-      testonly = true
     }
 
-    executable("lua_pictures") {
+    test_app("lua_pictures") {
       sources = [
         "tools/lua/lua_pictures.cpp",
       ]
@@ -1015,7 +1066,6 @@
         ":tool_utils",
         "//third_party/lua",
       ]
-      testonly = true
     }
   }
 
@@ -1046,7 +1096,7 @@
     }
   }
 
-  executable("dm") {
+  test_app("dm") {
     sources = [
       "dm/DM.cpp",
       "dm/DMJsonWriter.cpp",
@@ -1065,11 +1115,10 @@
       "//third_party/jsoncpp",
       "//third_party/libpng",
     ]
-    testonly = true
   }
 
   if (!is_debug) {  # I've benchmarked debug code once too many times...
-    executable("monobench") {
+    test_app("monobench") {
       sources = [
         "tools/monobench.cpp",
       ]
@@ -1077,11 +1126,10 @@
         ":bench",
         ":skia",
       ]
-      testonly = true
     }
   }
 
-  executable("nanobench") {
+  test_app("nanobench") {
     sources = [
       "bench/nanobench.cpp",
     ]
@@ -1096,11 +1144,10 @@
       ":tool_utils",
       "//third_party/jsoncpp",
     ]
-    testonly = true
   }
 
   if (is_linux || is_win || is_mac) {
-    executable("SampleApp") {
+    test_app("SampleApp") {
       sources = [
         "samplecode/SampleApp.cpp",
         "samplecode/SamplePictFile.cpp",
@@ -1124,12 +1171,11 @@
       if (skia_use_angle) {
         deps += [ "//third_party/angle2" ]
       }
-      testonly = true
     }
   }
 
   if (skia_enable_gpu) {
-    executable("skpbench") {
+    test_app("skpbench") {
       sources = [
         "tools/skpbench/skpbench.cpp",
       ]
@@ -1139,13 +1185,12 @@
         ":skia",
         ":tool_utils",
       ]
-      testonly = true
     }
   }
 
   # We can't yet build ICU on iOS or Windows.
   if (!is_ios && !is_win) {
-    executable("sktexttopdf-hb") {
+    test_app("sktexttopdf-hb") {
       sources = [
         "tools/SkShaper_harfbuzz.cpp",
         "tools/using_skia_and_harfbuzz.cpp",
@@ -1154,10 +1199,9 @@
         ":skia",
         "//third_party/harfbuzz",
       ]
-      testonly = true
     }
   }
-  executable("sktexttopdf") {
+  test_app("sktexttopdf") {
     sources = [
       "tools/SkShaper_primitive.cpp",
       "tools/using_skia_and_harfbuzz.cpp",
@@ -1165,10 +1209,9 @@
     deps = [
       ":skia",
     ]
-    testonly = true
   }
 
-  executable("get_images_from_skps") {
+  test_app("get_images_from_skps") {
     sources = [
       "tools/get_images_from_skps.cpp",
     ]
@@ -1177,10 +1220,9 @@
       ":skia",
       "//third_party/jsoncpp",
     ]
-    testonly = true
   }
 
-  executable("colorspaceinfo") {
+  test_app("colorspaceinfo") {
     sources = [
       "tools/colorspaceinfo.cpp",
     ]
@@ -1189,11 +1231,10 @@
       ":skia",
       ":tool_utils",
     ]
-    testonly = true
   }
 
   if (!is_ios) {
-    executable("skiaserve") {
+    test_app("skiaserve") {
       sources = [
         "tools/skiaserve/Request.cpp",
         "tools/skiaserve/Response.cpp",
@@ -1223,11 +1264,10 @@
         "//third_party/libmicrohttpd",
         "//third_party/libpng",
       ]
-      testonly = true
     }
   }
 
-  executable("fuzz") {
+  test_app("fuzz") {
     sources = [
       "fuzz/FilterFuzz.cpp",
       "fuzz/FuzzDrawFunctions.cpp",
@@ -1242,10 +1282,9 @@
       ":skia",
       ":tool_utils",
     ]
-    testonly = true
   }
 
-  executable("pathops_unittest") {
+  test_app("pathops_unittest") {
     sources = pathops_tests_sources + [
                 rebase_path("tests/skia_test.cpp"),
                 rebase_path("tests/Test.cpp"),
@@ -1256,10 +1295,9 @@
       ":skia",
       ":tool_utils",
     ]
-    testonly = true
   }
 
-  executable("dump_record") {
+  test_app("dump_record") {
     sources = [
       "tools/DumpRecord.cpp",
       "tools/dump_record.cpp",
@@ -1268,10 +1306,9 @@
       ":flags",
       ":skia",
     ]
-    testonly = true
   }
 
-  executable("skdiff") {
+  test_app("skdiff") {
     sources = [
       "tools/skdiff/skdiff.cpp",
       "tools/skdiff/skdiff_html.cpp",
@@ -1282,10 +1319,9 @@
       ":skia",
       ":tool_utils",
     ]
-    testonly = true
   }
 
-  executable("skp_parser") {
+  test_app("skp_parser") {
     sources = [
       "tools/skp_parser.cpp",
     ]
@@ -1294,11 +1330,11 @@
       ":tool_utils",
       "//third_party/jsoncpp",
     ]
-    testonly = true
   }
 
   if (skia_enable_gpu && (is_android || is_linux || is_win || is_mac)) {
-    source_set("viewer_sources") {
+    test_app("viewer") {
+      is_shared_library = is_android
       sources = [
         "tools/viewer/GMSlide.cpp",
         "tools/viewer/ImageSlide.cpp",
@@ -1373,28 +1409,33 @@
       } else if (is_mac) {
         deps += [ "//third_party/libsdl" ]
       }
-      testonly = true
     }
+  }
 
-    if (is_android) {
-      shared_library("libviewer") {
-        deps = [
-          ":viewer_sources",
+  if (is_android && is_skia_standalone && ndk != "") {
+    copy("gdbserver") {
+      sources = [
+        "$ndk/$ndk_gdbserver",
+      ]
+      outputs = [
+        "$root_out_dir/gdbserver",
+      ]
+    }
+    if (ndk_simpleperf != "") {
+      copy("simpleperf") {
+        sources = [
+          "$ndk/$ndk_simpleperf",
+          "$ndk/simpleperf/simpleperf_report.py",
         ]
-        testonly = true
-      }
-    } else {
-      executable("viewer") {
-        deps = [
-          ":viewer_sources",
+        outputs = [
+          "$root_out_dir/{{source_file_part}}",
         ]
-        testonly = true
       }
     }
   }
 
   if (skia_enable_gpu) {
-    executable("skslc") {
+    test_app("skslc") {
       sources = [
         "src/sksl/SkSLMain.cpp",
       ]
@@ -1402,7 +1443,6 @@
         ":flags",
         ":skia",
       ]
-      testonly = true
     }
   }
 }
diff --git a/DEPS b/DEPS
index 60c7ce9..f42e7d4 100644
--- a/DEPS
+++ b/DEPS
@@ -10,8 +10,8 @@
   # There is some duplication here that might be worth cleaning up:
   #   - can use use our existing t_p/e/libjpeg instead of pulling it for Android?
 
-  "third_party/externals/angle2"  : "https://chromium.googlesource.com/angle/angle.git@d445357315299e30c18c756f657ff928653128c2",
-  "third_party/externals/freetype": "https://skia.googlesource.com/third_party/freetype2.git@ffd8f6223607e9d61de33467fcd113f2a15dae36",
+  "third_party/externals/angle2"  : "https://chromium.googlesource.com/angle/angle.git@57f17473791703ac82add77c3d77d13d8e2dfbc4",
+  "third_party/externals/freetype": "https://skia.googlesource.com/third_party/freetype2.git@08fd250e1af0aa16d18012d39462e6ca9bbc6e90",
   "third_party/externals/gyp"     : "https://chromium.googlesource.com/external/gyp.git@87ac4d0e63fc7dd8152a350327fea8dcf031bf56",
   "third_party/externals/harfbuzz": "https://skia.googlesource.com/third_party/harfbuzz.git@1.3.0",
   "third_party/externals/jsoncpp" : "https://chromium.googlesource.com/external/github.com/open-source-parsers/jsoncpp.git@1.0.0",
diff --git a/bench/AndroidCodecBench.h b/bench/AndroidCodecBench.h
index 8cdd7fe..528ecab 100644
--- a/bench/AndroidCodecBench.h
+++ b/bench/AndroidCodecBench.h
@@ -9,6 +9,7 @@
 #define AndroidCodecBench_DEFINED
 
 #include "Benchmark.h"
+#include "SkAutoMalloc.h"
 #include "SkData.h"
 #include "SkImageInfo.h"
 #include "SkRefCnt.h"
diff --git a/bench/Benchmark.h b/bench/Benchmark.h
index 30c7aa9..8fc75f8 100644
--- a/bench/Benchmark.h
+++ b/bench/Benchmark.h
@@ -11,7 +11,7 @@
 #include "SkPoint.h"
 #include "SkRefCnt.h"
 #include "SkString.h"
-#include "SkTRegistry.h"
+#include "../tools/Registry.h"
 
 #define DEF_BENCH3(code, N) \
     static BenchRegistry gBench##N([](void*) -> Benchmark* { code; });
@@ -150,6 +150,6 @@
     typedef SkRefCnt INHERITED;
 };
 
-typedef SkTRegistry<Benchmark*(*)(void*)> BenchRegistry;
+typedef sk_tools::Registry<Benchmark*(*)(void*)> BenchRegistry;
 
 #endif
diff --git a/bench/CodecBench.h b/bench/CodecBench.h
index 5b4db24..dc44704 100644
--- a/bench/CodecBench.h
+++ b/bench/CodecBench.h
@@ -9,6 +9,7 @@
 #define CodecBench_DEFINED
 
 #include "Benchmark.h"
+#include "SkAutoMalloc.h"
 #include "SkData.h"
 #include "SkImageInfo.h"
 #include "SkRefCnt.h"
diff --git a/bench/ColorCodecBench.h b/bench/ColorCodecBench.h
index 9159bcf..e5048f8 100644
--- a/bench/ColorCodecBench.h
+++ b/bench/ColorCodecBench.h
@@ -9,6 +9,7 @@
 #define ColorCodecBench_DEFINED
 
 #include "Benchmark.h"
+#include "SkAutoMalloc.h"
 #include "SkData.h"
 #include "SkImageInfo.h"
 
diff --git a/bench/PDFBench.cpp b/bench/PDFBench.cpp
index 573353f..e0d4934 100644
--- a/bench/PDFBench.cpp
+++ b/bench/PDFBench.cpp
@@ -6,15 +6,12 @@
  */
 
 #include "Benchmark.h"
+
 #include "Resources.h"
 #include "SkAutoPixmapStorage.h"
 #include "SkData.h"
 #include "SkGradientShader.h"
 #include "SkImage.h"
-#include "SkPDFBitmap.h"
-#include "SkPDFDocument.h"
-#include "SkPDFShader.h"
-#include "SkPDFUtils.h"
 #include "SkPixmap.h"
 #include "SkRandom.h"
 #include "SkStream.h"
@@ -27,6 +24,33 @@
     size_t fN;
 };
 
+struct WStreamWriteTextBenchmark : public Benchmark {
+    std::unique_ptr<SkWStream> fWStream;
+    WStreamWriteTextBenchmark() : fWStream(new NullWStream) {}
+    const char* onGetName() override { return "WStreamWriteText"; }
+    bool isSuitableFor(Backend backend) override {
+        return backend == kNonRendering_Backend;
+    }
+    void onDraw(int loops, SkCanvas*) override {
+        while (loops-- > 0) {
+            for (int i = 1000; i-- > 0;) {
+                fWStream->writeText("HELLO SKIA!\n");
+            }
+        }
+    }
+};
+}  // namespace
+
+DEF_BENCH(return new WStreamWriteTextBenchmark;)
+
+#ifdef SK_SUPPORT_PDF
+
+#include "SkPDFBitmap.h"
+#include "SkPDFDocument.h"
+#include "SkPDFShader.h"
+#include "SkPDFUtils.h"
+
+namespace {
 static void test_pdf_object_serialization(const sk_sp<SkPDFObject> object) {
     // SkDebugWStream wStream;
     NullWStream wStream;
@@ -207,22 +231,6 @@
     }
 };
 
-struct WStreamWriteTextBenchmark : public Benchmark {
-    std::unique_ptr<SkWStream> fWStream;
-    WStreamWriteTextBenchmark() : fWStream(new NullWStream) {}
-    const char* onGetName() override { return "WStreamWriteText"; }
-    bool isSuitableFor(Backend backend) override {
-        return backend == kNonRendering_Backend;
-    }
-    void onDraw(int loops, SkCanvas*) override {
-        while (loops-- > 0) {
-            for (int i = 1000; i-- > 0;) {
-                fWStream->writeText("HELLO SKIA!\n");
-            }
-        }
-    }
-};
-
 struct WritePDFTextBenchmark : public Benchmark {
     std::unique_ptr<SkWStream> fWStream;
     WritePDFTextBenchmark() : fWStream(new NullWStream) {}
@@ -249,5 +257,7 @@
 DEF_BENCH(return new PDFScalarBench;)
 DEF_BENCH(return new PDFColorComponentBench;)
 DEF_BENCH(return new PDFShaderBench;)
-DEF_BENCH(return new WStreamWriteTextBenchmark;)
 DEF_BENCH(return new WritePDFTextBenchmark;)
+
+#endif
+
diff --git a/bench/PathBench.cpp b/bench/PathBench.cpp
index 4ca4401..449d305 100644
--- a/bench/PathBench.cpp
+++ b/bench/PathBench.cpp
@@ -173,6 +173,23 @@
     typedef PathBench INHERITED;
 };
 
+class NonAACirclePathBench: public CirclePathBench {
+public:
+    NonAACirclePathBench(Flags flags) : INHERITED(flags) {}
+
+    void appendName(SkString* name) override {
+        name->append("nonaacircle");
+    }
+
+    void setupPaint(SkPaint* paint) override {
+        CirclePathBench::setupPaint(paint);
+        paint->setAntiAlias(false);
+    }
+
+private:
+    typedef CirclePathBench INHERITED;
+};
+
 // Test max speedup of Analytic AA for concave paths
 class AAAConcavePathBench : public PathBench {
 public:
@@ -1090,6 +1107,9 @@
 DEF_BENCH( return new CirclePathBench(FLAGS10); )
 DEF_BENCH( return new CirclePathBench(FLAGS11); )
 
+DEF_BENCH( return new NonAACirclePathBench(FLAGS00); )
+DEF_BENCH( return new NonAACirclePathBench(FLAGS10); )
+
 DEF_BENCH( return new AAAConcavePathBench(FLAGS00); )
 DEF_BENCH( return new AAAConcavePathBench(FLAGS10); )
 DEF_BENCH( return new AAAConvexPathBench(FLAGS00); )
diff --git a/bench/SkRasterPipelineBench.cpp b/bench/SkRasterPipelineBench.cpp
index 16dea8a..6d9bb49 100644
--- a/bench/SkRasterPipelineBench.cpp
+++ b/bench/SkRasterPipelineBench.cpp
@@ -22,13 +22,18 @@
 //   - src = srcover(dst, src)
 //   - store src back as srgb/f16
 
-template <bool kF16>
+template <bool kF16, bool kCompiled>
 class SkRasterPipelineBench : public Benchmark {
 public:
     bool isSuitableFor(Backend backend) override { return backend == kNonRendering_Backend; }
     const char* onGetName() override {
-        return kF16 ? "SkRasterPipeline_f16"
-                    : "SkRasterPipeline_srgb";
+        switch ((int)kCompiled << 1 | (int)kF16) {
+            case 0: return "SkRasterPipeline_srgb_run";
+            case 1: return "SkRasterPipeline_f16_run";
+            case 2: return "SkRasterPipeline_srgb_compile";
+            case 3: return "SkRasterPipeline_f16_compile";
+        }
+        return "whoops";
     }
 
     void onDraw(int loops, SkCanvas*) override {
@@ -40,25 +45,34 @@
         p.append(SkRasterPipeline::load_8888, &src_ctx);
         p.append_from_srgb(kUnpremul_SkAlphaType);
         p.append(SkRasterPipeline::scale_u8, &mask_ctx);
+        p.append(SkRasterPipeline::move_src_dst);
         if (kF16) {
-            p.append(SkRasterPipeline::load_f16_d, &dst_ctx);
+            p.append(SkRasterPipeline::load_f16, &dst_ctx);
         } else {
-            p.append(SkRasterPipeline::load_8888_d, &dst_ctx);
-            p.append_from_srgb_d(kPremul_SkAlphaType);
+            p.append(SkRasterPipeline::load_8888, &dst_ctx);
+            p.append_from_srgb(kPremul_SkAlphaType);
         }
-        p.append(SkRasterPipeline::srcover);
+        p.append(SkRasterPipeline::dstover);
         if (kF16) {
             p.append(SkRasterPipeline::store_f16, &dst_ctx);
         } else {
             p.append(SkRasterPipeline::to_srgb);
             p.append(SkRasterPipeline::store_8888, &dst_ctx);
         }
-        auto compiled = p.compile();
 
-        while (loops --> 0) {
-            compiled(0,0, N);
+        if (kCompiled) {
+            auto compiled = p.compile();
+            while (loops --> 0) {
+                compiled(0,0, N);
+            }
+        } else {
+            while (loops --> 0) {
+                p.run(0,0, N);
+            }
         }
     }
 };
-DEF_BENCH( return new SkRasterPipelineBench<true>; )
-DEF_BENCH( return new SkRasterPipelineBench<false>; )
+DEF_BENCH( return (new SkRasterPipelineBench< true,  true>); )
+DEF_BENCH( return (new SkRasterPipelineBench<false,  true>); )
+DEF_BENCH( return (new SkRasterPipelineBench< true, false>); )
+DEF_BENCH( return (new SkRasterPipelineBench<false, false>); )
diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp
index f1c08d7..34aaee8 100644
--- a/bench/nanobench.cpp
+++ b/bench/nanobench.cpp
@@ -25,8 +25,9 @@
 #include "Stats.h"
 
 #include "SkAndroidCodec.h"
-#include "SkBitmapRegionDecoder.h"
+#include "SkAutoMalloc.h"
 #include "SkBBoxHierarchy.h"
+#include "SkBitmapRegionDecoder.h"
 #include "SkCanvas.h"
 #include "SkCodec.h"
 #include "SkCommonFlags.h"
@@ -38,13 +39,13 @@
 #include "SkOSPath.h"
 #include "SkPictureRecorder.h"
 #include "SkPictureUtils.h"
+#include "SkSVGDOM.h"
+#include "SkScan.h"
 #include "SkString.h"
 #include "SkSurface.h"
-#include "SkSVGDOM.h"
 #include "SkTaskGroup.h"
 #include "SkThreadUtils.h"
 #include "ThermalManager.h"
-#include "SkScan.h"
 
 #include <stdlib.h>
 
@@ -328,7 +329,7 @@
     //  -------------------------  < FLAGS_overheadGoal
     //  overhead + N * Bench Time
     //
-    // where bench_plus_overhead ≈ overhead + Bench Time.
+    // where bench_plus_overhead ~=~ overhead + Bench Time.
     //
     // Doing some math, we get:
     //
diff --git a/debugger/QT/SkGLWidget.cpp b/debugger/QT/SkGLWidget.cpp
index f95af84..a60ae0f 100644
--- a/debugger/QT/SkGLWidget.cpp
+++ b/debugger/QT/SkGLWidget.cpp
@@ -83,10 +83,10 @@
     desc.fWidth = SkScalarRoundToInt(this->width());
     desc.fHeight = SkScalarRoundToInt(this->height());
     desc.fConfig = kSkia8888_GrPixelConfig;
-    GR_GL_GetIntegerv(fCurIntf, GR_GL_SAMPLES, &desc.fSampleCnt);
-    GR_GL_GetIntegerv(fCurIntf, GR_GL_STENCIL_BITS, &desc.fStencilBits);
+    GR_GL_GetIntegerv(fCurIntf.get(), GR_GL_SAMPLES, &desc.fSampleCnt);
+    GR_GL_GetIntegerv(fCurIntf.get(), GR_GL_STENCIL_BITS, &desc.fStencilBits);
     GrGLint buffer;
-    GR_GL_GetIntegerv(fCurIntf, GR_GL_FRAMEBUFFER_BINDING, &buffer);
+    GR_GL_GetIntegerv(fCurIntf.get(), GR_GL_FRAMEBUFFER_BINDING, &buffer);
     desc.fRenderTargetHandle = buffer;
 
     return desc;
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 021b411..7774d5e 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -7,7 +7,6 @@
 
 #include "DMJsonWriter.h"
 #include "DMSrcSink.h"
-#include "DMSrcSinkAndroid.h"
 #include "ProcStats.h"
 #include "Resources.h"
 #include "SkBBHFactory.h"
@@ -859,10 +858,6 @@
 
 #define SINK(t, sink, ...) if (config->getBackend().equals(t)) { return new sink(__VA_ARGS__); }
 
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    SINK("hwui",           HWUISink);
-#endif
-
     if (FLAGS_cpu) {
         auto srgbColorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
         auto srgbLinearColorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGBLinear_Named);
@@ -908,10 +903,6 @@
         VIA("upright", ViaUpright, m, wrapped);
     }
 
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    VIA("androidsdk", ViaAndroidSDK, wrapped);
-#endif
-
 #undef VIA
     return nullptr;
 }
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 594a4b7..cea87f7 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -8,11 +8,12 @@
 #include "DMSrcSink.h"
 #include "Resources.h"
 #include "SkAndroidCodec.h"
+#include "SkAutoMalloc.h"
 #include "SkCodec.h"
 #include "SkCodecImageGenerator.h"
 #include "SkColorSpace.h"
-#include "SkColorSpace_XYZ.h"
 #include "SkColorSpaceXform.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkCommonFlags.h"
 #include "SkData.h"
 #include "SkDebugCanvas.h"
@@ -37,10 +38,10 @@
 #include "SkRecorder.h"
 #include "SkSVGCanvas.h"
 #include "SkStream.h"
-#include "SkTLogic.h"
 #include "SkSwizzler.h"
-#include <functional>
+#include "SkTLogic.h"
 #include <cmath>
+#include <functional>
 
 #if defined(SK_BUILD_FOR_WIN)
     #include "SkAutoCoInitialize.h"
diff --git a/dm/DMSrcSinkAndroid.cpp b/dm/DMSrcSinkAndroid.cpp
deleted file mode 100644
index da2b924..0000000
--- a/dm/DMSrcSinkAndroid.cpp
+++ /dev/null
@@ -1,69 +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 "DMSrcSink.h"
-#include "DMSrcSinkAndroid.h"
-
-#include "SkAndroidSDKCanvas.h"
-#include "SkCanvas.h"
-#include "SkiaCanvasProxy.h"
-#include "SkStream.h"
-#include <utils/TestWindowContext.h>
-
-/* These functions are only compiled in the Android Framework. */
-
-namespace DM {
-
-Error HWUISink::draw(const Src& src, SkBitmap* dst, SkWStream*, SkString*) const {
-    android::uirenderer::TestWindowContext renderer;
-    renderer.initialize(src.size().width(), src.size().height());
-    SkCanvas* canvas = renderer.prepareToDraw();
-    Error err = src.draw(canvas);
-    if (!err.isEmpty()) {
-        return err;
-    }
-    renderer.finishDrawing();
-    renderer.fence();
-    renderer.capturePixels(dst);
-    return "";
-}
-
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-ViaAndroidSDK::ViaAndroidSDK(Sink* sink) : fSink(sink) { }
-
-Error ViaAndroidSDK::draw(const Src& src,
-                          SkBitmap* bitmap,
-                          SkWStream* stream,
-                          SkString* log) const {
-    struct ProxySrc : public Src {
-        const Src& fSrc;
-        ProxySrc(const Src& src)
-            : fSrc(src) {}
-
-        Error draw(SkCanvas* canvas) const override {
-            // Pass through HWUI's upper layers to get operational transforms
-            std::unique_ptr<android::Canvas> ac(android::Canvas::create_canvas(canvas));
-            std::unique_ptr<android::uirenderer::SkiaCanvasProxy> scProxy
-                (new android::uirenderer::SkiaCanvasProxy(ac.get()));
-
-            // Pass through another proxy to get paint transforms
-            SkAndroidSDKCanvas fc;
-            fc.reset(scProxy.get());
-
-            fSrc.draw(&fc);
-
-            return "";
-        }
-        SkISize size() const override { return fSrc.size(); }
-        Name name() const override { sk_throw(); return ""; }
-    } proxy(src);
-
-    return fSink->draw(proxy, bitmap, stream, log);
-}
-
-}  // namespace DM
diff --git a/dm/DMSrcSinkAndroid.h b/dm/DMSrcSinkAndroid.h
deleted file mode 100644
index 6717df6..0000000
--- a/dm/DMSrcSinkAndroid.h
+++ /dev/null
@@ -1,58 +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 DMSrcSinkAndroid_DEFINED
-#define DMSrcSinkAndroid_DEFINED
-
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-
-#include "DMSrcSink.h"
-
-namespace DM {
-
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-// Draws to the Android Framework's HWUI API.
-
-class HWUISink : public Sink {
-public:
-    HWUISink() { }
-
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
-    bool serial() const override { return true; }
-    const char* fileExtension() const override { return "png"; }
-    SinkFlags flags() const override { return SinkFlags{ SinkFlags::kGPU, SinkFlags::kDirect }; }
-};
-
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-// Trims draw commands to only include those supported by the Android Framework's HWUI API.
-
-class ViaAndroidSDK : public Sink {
-public:
-    explicit ViaAndroidSDK(Sink*);
-
-    Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
-    bool serial() const override { return fSink->serial(); }
-    const char* fileExtension() const override { return fSink->fileExtension(); }
-    SinkFlags flags() const override {
-        SinkFlags flags = fSink->flags();
-        flags.approach = SinkFlags::kIndirect;
-        return flags;
-    }
-
-private:
-    std::unique_ptr<Sink> fSink;
-};
-
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-}  // namespace DM
-
-#endif  // SK_BUILD_FOR_ANDROID_FRAMEWORK
-
-#endif  // DMSrcSinkAndroid_DEFINED
diff --git a/fuzz/Fuzz.h b/fuzz/Fuzz.h
index 800a9f1..e6399a8 100644
--- a/fuzz/Fuzz.h
+++ b/fuzz/Fuzz.h
@@ -9,7 +9,7 @@
 #define Fuzz_DEFINED
 
 #include "SkData.h"
-#include "SkTRegistry.h"
+#include "../tools/Registry.h"
 #include "SkTypes.h"
 
 #include <cmath>
@@ -126,9 +126,9 @@
     void (*fn)(Fuzz*);
 };
 
-#define DEF_FUZZ(name, f)                                        \
-    static void fuzz_##name(Fuzz*);                              \
-    SkTRegistry<Fuzzable> register_##name({#name, fuzz_##name}); \
+#define DEF_FUZZ(name, f)                                               \
+    static void fuzz_##name(Fuzz*);                                     \
+    sk_tools::Registry<Fuzzable> register_##name({#name, fuzz_##name}); \
     static void fuzz_##name(Fuzz* f)
 
 #endif//Fuzz_DEFINED
diff --git a/fuzz/fuzz.cpp b/fuzz/fuzz.cpp
index f8f02a3..bf3b88c 100644
--- a/fuzz/fuzz.cpp
+++ b/fuzz/fuzz.cpp
@@ -13,8 +13,9 @@
 #include "SkImage.h"
 #include "SkImageEncoder.h"
 #include "SkMallocPixelRef.h"
-#include "SkPicture.h"
-#include "SkPicture.h"
+#include "SkPath.h"
+#include "SkOSFile.h"
+#include "SkOSPath.h"
 #include "SkPicture.h"
 #if SK_SUPPORT_GPU
 #include "SkSLCompiler.h"
@@ -25,65 +26,96 @@
 
 #include "sk_tool_utils.h"
 
-DEFINE_string2(bytes, b, "", "A path to a file.  This can be the fuzz bytes or a binary to parse.");
+DEFINE_string2(bytes, b, "", "A path to a file or a directory. If a file, the contents will be used as the fuzz bytes. If a directory, all files in the directory will be used as fuzz bytes for the fuzzer, one at a time.");
 DEFINE_string2(name, n, "", "If --type is 'api', fuzz the API with this name.");
 
 DEFINE_string2(type, t, "api", "How to interpret --bytes, either 'image_scale', 'image_mode', 'skp', 'icc', or 'api'.");
 DEFINE_string2(dump, d, "", "If not empty, dump 'image*' or 'skp' types as a PNG with this name.");
 
-static int printUsage(const char* name) {
-    SkDebugf("Usage: %s -t <type> -b <path/to/file> [-n api-to-fuzz]\n", name);
+static int printUsage() {
+    SkDebugf("Usage: fuzz -t <type> -b <path/to/file> [-n api-to-fuzz]\n");
     return 1;
 }
+static int fuzz_file(const char* path);
 static uint8_t calculate_option(SkData*);
 
-static int fuzz_api(sk_sp<SkData>);
-static int fuzz_img(sk_sp<SkData>, uint8_t, uint8_t);
-static int fuzz_skp(sk_sp<SkData>);
-static int fuzz_icc(sk_sp<SkData>);
-static int fuzz_color_deserialize(sk_sp<SkData>);
+static void fuzz_api(sk_sp<SkData>);
+static void fuzz_img(sk_sp<SkData>, uint8_t, uint8_t);
+static void fuzz_skp(sk_sp<SkData>);
+static void fuzz_icc(sk_sp<SkData>);
+static void fuzz_color_deserialize(sk_sp<SkData>);
+static void fuzz_path_deserialize(sk_sp<SkData>);
 #if SK_SUPPORT_GPU
-static int fuzz_sksl2glsl(sk_sp<SkData>);
+static void fuzz_sksl2glsl(sk_sp<SkData>);
 #endif
 
 int main(int argc, char** argv) {
     SkCommandLineFlags::Parse(argc, argv);
 
     const char* path = FLAGS_bytes.isEmpty() ? argv[0] : FLAGS_bytes[0];
+
+    if (!sk_isdir(path)) {
+        return fuzz_file(path);
+    }
+
+    SkOSFile::Iter it(path);
+    for (SkString file; it.next(&file); ) {
+        SkString p = SkOSPath::Join(path, file.c_str());
+        SkDebugf("Fuzzing %s\n", p.c_str());
+        int rv = fuzz_file(p.c_str());
+        if (rv != 0) {
+            return rv;
+        }
+    }
+    return 0;
+}
+
+static int fuzz_file(const char* path) {
     sk_sp<SkData> bytes(SkData::MakeFromFileName(path));
     if (!bytes) {
         SkDebugf("Could not read %s\n", path);
-        return 2;
+        return 1;
     }
 
     uint8_t option = calculate_option(bytes.get());
 
     if (!FLAGS_type.isEmpty()) {
         if (0 == strcmp("api", FLAGS_type[0])) {
-            return fuzz_api(bytes);
+            fuzz_api(bytes);
+            return 0;
         }
         if (0 == strcmp("color_deserialize", FLAGS_type[0])) {
-            return fuzz_color_deserialize(bytes);
+            fuzz_color_deserialize(bytes);
+            return 0;
         }
         if (0 == strcmp("icc", FLAGS_type[0])) {
-            return fuzz_icc(bytes);
+            fuzz_icc(bytes);
+            return 0;
         }
         if (0 == strcmp("image_scale", FLAGS_type[0])) {
-            return fuzz_img(bytes, option, 0);
+            fuzz_img(bytes, option, 0);
+            return 0;
         }
         if (0 == strcmp("image_mode", FLAGS_type[0])) {
-            return fuzz_img(bytes, 0, option);
+            fuzz_img(bytes, 0, option);
+            return 0;
+        }
+        if (0 == strcmp("path_deserialize", FLAGS_type[0])) {
+            fuzz_path_deserialize(bytes);
+            return 0;
         }
         if (0 == strcmp("skp", FLAGS_type[0])) {
-            return fuzz_skp(bytes);
+            fuzz_skp(bytes);
+            return 0;
         }
 #if SK_SUPPORT_GPU
         if (0 == strcmp("sksl2glsl", FLAGS_type[0])) {
-            return fuzz_sksl2glsl(bytes);
+            fuzz_sksl2glsl(bytes);
+            return 0;
         }
 #endif
     }
-    return printUsage(argv[0]);
+    return printUsage();
 }
 
 // This adds up the first 1024 bytes and returns it as an 8 bit integer.  This allows afl-fuzz to
@@ -100,26 +132,25 @@
     return total;
 }
 
-int fuzz_api(sk_sp<SkData> bytes) {
+static void fuzz_api(sk_sp<SkData> bytes) {
     const char* name = FLAGS_name.isEmpty() ? "" : FLAGS_name[0];
 
-    for (auto r = SkTRegistry<Fuzzable>::Head(); r; r = r->next()) {
+    for (auto r = sk_tools::Registry<Fuzzable>::Head(); r; r = r->next()) {
         auto fuzzable = r->factory();
         if (0 == strcmp(name, fuzzable.name)) {
             SkDebugf("Fuzzing %s...\n", fuzzable.name);
             Fuzz fuzz(bytes);
             fuzzable.fn(&fuzz);
             SkDebugf("[terminated] Success!\n");
-            return 0;
+            return;
         }
     }
 
     SkDebugf("When using --type api, please choose an API to fuzz with --name/-n:\n");
-    for (auto r = SkTRegistry<Fuzzable>::Head(); r; r = r->next()) {
+    for (auto r = sk_tools::Registry<Fuzzable>::Head(); r; r = r->next()) {
         auto fuzzable = r->factory();
         SkDebugf("\t%s\n", fuzzable.name);
     }
-    return 1;
 }
 
 static void dump_png(SkBitmap bitmap) {
@@ -129,7 +160,7 @@
     }
 }
 
-int fuzz_img(sk_sp<SkData> bytes, uint8_t scale, uint8_t mode) {
+static void fuzz_img(sk_sp<SkData> bytes, uint8_t scale, uint8_t mode) {
     // We can scale 1x, 2x, 4x, 8x, 16x
     scale = scale % 5;
     float fscale = (float)pow(2.0f, scale);
@@ -144,7 +175,7 @@
     std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(bytes));
     if (nullptr == codec.get()) {
         SkDebugf("[terminated] Couldn't create codec.\n");
-        return 3;
+        return;
     }
 
     SkImageInfo decodeInfo = codec->getInfo();
@@ -177,7 +208,7 @@
     if (!bitmap.tryAllocPixels(decodeInfo, &zeroFactory, colorTable.get())) {
         SkDebugf("[terminated] Could not allocate memory.  Image might be too large (%d x %d)",
                  decodeInfo.width(), decodeInfo.height());
-        return 4;
+        return;
     }
 
     switch (mode) {
@@ -196,7 +227,7 @@
                     raise(SIGSEGV);
                 default:
                     SkDebugf("[terminated] Couldn't getPixels.\n");
-                    return 6;
+                    return;
             }
             break;
         }
@@ -204,7 +235,7 @@
             if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
                                                                 colorCountPtr)) {
                     SkDebugf("[terminated] Could not start scanline decoder\n");
-                    return 7;
+                    return;
                 }
 
             void* dst = bitmap.getAddr(0, 0);
@@ -236,7 +267,7 @@
                 // Jpegs have kTopDown_SkScanlineOrder, and at this time, it is not interesting
                 // to run this test for image types that do not have this scanline ordering.
                 SkDebugf("[terminated] Could not start top-down scanline decoder\n");
-                return 8;
+                return;
             }
 
             for (int i = 0; i < numStripes; i += 2) {
@@ -257,7 +288,7 @@
                     colorPtr, colorCountPtr);
             if (SkCodec::kSuccess != startResult) {
                 SkDebugf("[terminated] Failed to restart scanline decoder with same parameters.\n");
-                return 9;
+                return;
             }
             for (int i = 0; i < numStripes; i += 2) {
                 // Read a stripe
@@ -283,7 +314,7 @@
             if (divisor > W || divisor > H) {
                 SkDebugf("[terminated] Cannot codec subset: divisor %d is too big "
                          "with dimensions (%d x %d)\n", divisor, W, H);
-                return 10;
+                return;
             }
             // subset dimensions
             // SkWebpCodec, the only one that supports subsets, requires even top/left boundaries.
@@ -316,7 +347,7 @@
                     if (!subsetBm.installPixels(decodeInfo, pixels, rowBytes, colorTable.get(),
                                                 nullptr, nullptr)) {
                         SkDebugf("[terminated] Could not install pixels.\n");
-                        return 11;
+                        return;
                     }
                     const SkCodec::Result result = codec->getPixels(decodeInfo, pixels, rowBytes,
                             &opts, colorPtr, colorCountPtr);
@@ -329,7 +360,7 @@
                             if (0 == (x|y)) {
                                 // First subset is okay to return unimplemented.
                                 SkDebugf("[terminated] Incompatible colortype conversion\n");
-                                return 12;
+                                return;
                             }
                             // If the first subset succeeded, a later one should not fail.
                             // fall through to failure
@@ -337,7 +368,7 @@
                             if (0 == (x|y)) {
                                 // First subset is okay to return unimplemented.
                                 SkDebugf("[terminated] subset codec not supported\n");
-                                return 13;
+                                return;
                             }
                             // If the first subset succeeded, why would a later one fail?
                             // fall through to failure
@@ -346,7 +377,7 @@
                                                   "with dimensions (%d x %d)\t error %d\n",
                                                   x, y, decodeInfo.width(), decodeInfo.height(),
                                                   W, H, result);
-                            return 14;
+                            return;
                     }
                     // translate by the scaled height.
                     top += decodeInfo.height();
@@ -371,7 +402,7 @@
                 if (SkCodec::kSuccess != result) {
                     SkDebugf("[terminated] failed to start incremental decode "
                              "in frame %d with error %d\n", i, result);
-                    return 15;
+                    return;
                 }
 
                 result = codec->incrementalDecode();
@@ -385,7 +416,7 @@
                 } else {
                     SkDebugf("[terminated] incremental decode failed with "
                              "error %d\n", result);
-                    return 16;
+                    return;
                 }
             }
             SkDebugf("[terminated] Success!\n");
@@ -396,16 +427,15 @@
     }
 
     dump_png(bitmap);
-    return 0;
 }
 
-int fuzz_skp(sk_sp<SkData> bytes) {
+static void fuzz_skp(sk_sp<SkData> bytes) {
     SkMemoryStream stream(bytes);
     SkDebugf("Decoding\n");
     sk_sp<SkPicture> pic(SkPicture::MakeFromStream(&stream));
     if (!pic) {
         SkDebugf("[terminated] Couldn't decode as a picture.\n");
-        return 3;
+        return;
     }
     SkDebugf("Rendering\n");
     SkBitmap bitmap;
@@ -417,31 +447,37 @@
     canvas.drawPicture(pic);
     SkDebugf("[terminated] Success! Decoded and rendered an SkPicture!\n");
     dump_png(bitmap);
-    return 0;
 }
 
-int fuzz_icc(sk_sp<SkData> bytes) {
+static void fuzz_icc(sk_sp<SkData> bytes) {
     sk_sp<SkColorSpace> space(SkColorSpace::MakeICC(bytes->data(), bytes->size()));
     if (!space) {
         SkDebugf("[terminated] Couldn't decode ICC.\n");
-        return 1;
+        return;
     }
     SkDebugf("[terminated] Success! Decoded ICC.\n");
-    return 0;
 }
 
-int fuzz_color_deserialize(sk_sp<SkData> bytes) {
+static void fuzz_color_deserialize(sk_sp<SkData> bytes) {
     sk_sp<SkColorSpace> space(SkColorSpace::Deserialize(bytes->data(), bytes->size()));
     if (!space) {
         SkDebugf("[terminated] Couldn't deserialize Colorspace.\n");
-        return 1;
+        return;
     }
     SkDebugf("[terminated] Success! deserialized Colorspace.\n");
-    return 0;
+}
+
+static void fuzz_path_deserialize(sk_sp<SkData> bytes) {
+    SkPath path;
+    if (!path.readFromMemory(bytes->data(), bytes->size())) {
+        SkDebugf("[terminated] Couldn't initialize SkPath.\n");
+        return;
+    }
+    SkDebugf("[terminated] Success! Initialized SkPath.\n");
 }
 
 #if SK_SUPPORT_GPU
-int fuzz_sksl2glsl(sk_sp<SkData> bytes) {
+static void fuzz_sksl2glsl(sk_sp<SkData> bytes) {
     SkSL::Compiler compiler;
     SkString output;
     SkSL::Program::Settings settings;
@@ -452,10 +488,9 @@
                                                               settings);
     if (!program || !compiler.toGLSL(*program, &output)) {
         SkDebugf("[terminated] Couldn't compile input.\n");
-        return 1;
+        return;
     }
     SkDebugf("[terminated] Success! Compiled input.\n");
-    return 0;
 }
 #endif
 
diff --git a/gm/arithmode.cpp b/gm/arithmode.cpp
index caa3e28..aec079c 100644
--- a/gm/arithmode.cpp
+++ b/gm/arithmode.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "gm.h"
+#include "SkArithmeticImageFilter.h"
 #include "SkCanvas.h"
 #include "SkColorPriv.h"
 #include "SkGradientShader.h"
@@ -13,7 +14,6 @@
 #include "SkImageSource.h"
 #include "SkShader.h"
 #include "SkSurface.h"
-#include "SkXfermodeImageFilter.h"
 
 #define WW  100
 #define HH  32
@@ -109,8 +109,8 @@
                 canvas->drawImage(dst, 0, 0);
                 canvas->translate(gap, 0);
                 SkPaint paint;
-                paint.setImageFilter(SkXfermodeImageFilter::MakeArithmetic(k[0], k[1], k[2], k[3],
-                                     true, dstFilter, srcFilter, nullptr));
+                paint.setImageFilter(SkArithmeticImageFilter::Make(k[0], k[1], k[2], k[3], true,
+                                                                   dstFilter, srcFilter, nullptr));
                 canvas->saveLayer(&rect, &paint);
                 canvas->restore();
 
@@ -137,12 +137,10 @@
                 canvas->translate(gap, 0);
 
                 sk_sp<SkImageFilter> bg =
-                    SkXfermodeImageFilter::MakeArithmetic(0, 0, -one / 2, 1, enforcePMColor,
-                                                          dstFilter);
+                        SkArithmeticImageFilter::Make(0, 0, -one / 2, 1, enforcePMColor, dstFilter);
                 SkPaint p;
-                p.setImageFilter(SkXfermodeImageFilter::MakeArithmetic(0, one / 2, -one, 1, true,
-                                                                       std::move(bg), dstFilter,
-                                                                       nullptr));
+                p.setImageFilter(SkArithmeticImageFilter::Make(0, one / 2, -one, 1, true,
+                                                               std::move(bg), dstFilter, nullptr));
                 canvas->saveLayer(&rect, &p);
                 canvas->restore();
                 canvas->translate(gap, 0);
diff --git a/gm/beziereffects.cpp b/gm/beziereffects.cpp
index e529ded..cc28538 100644
--- a/gm/beziereffects.cpp
+++ b/gm/beziereffects.cpp
@@ -34,9 +34,10 @@
 
     const char* name() const override { return "BezierCubicOrConicTestOp"; }
 
-    static sk_sp<GrDrawOp> Make(sk_sp<GrGeometryProcessor> gp, const SkRect& bounds, GrColor color,
-                                const SkScalar klmEqs[9], SkScalar sign) {
-        return sk_sp<GrDrawOp>(new BezierCubicOrConicTestOp(gp, bounds, color, klmEqs, sign));
+    static std::unique_ptr<GrDrawOp> Make(sk_sp<GrGeometryProcessor> gp, const SkRect& bounds,
+                                          GrColor color, const SkScalar klmEqs[9], SkScalar sign) {
+        return std::unique_ptr<GrDrawOp>(
+                new BezierCubicOrConicTestOp(gp, bounds, color, klmEqs, sign));
     }
 
 private:
@@ -192,13 +193,13 @@
                     canvas->drawRect(bounds, boundsPaint);
 
                     GrPaint grPaint;
-                    grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+                    grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
 
-                    sk_sp<GrDrawOp> op =
+                    std::unique_ptr<GrDrawOp> op =
                             BezierCubicOrConicTestOp::Make(gp, bounds, color, klmEqs, klmSigns[c]);
 
-                    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                      std::move(op));
+                    renderTargetContext->priv().testingOnly_addDrawOp(
+                            std::move(grPaint), GrAAType::kNone, std::move(op));
                 }
                 ++col;
                 if (numCols == col) {
@@ -325,13 +326,13 @@
                     canvas->drawRect(bounds, boundsPaint);
 
                     GrPaint grPaint;
-                    grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+                    grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
 
-                    sk_sp<GrDrawOp> op =
+                    std::unique_ptr<GrDrawOp> op =
                             BezierCubicOrConicTestOp::Make(gp, bounds, color, klmEqs, 1.f);
 
-                    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                      std::move(op));
+                    renderTargetContext->priv().testingOnly_addDrawOp(
+                            std::move(grPaint), GrAAType::kNone, std::move(op));
                 }
                 ++col;
                 if (numCols == col) {
@@ -393,9 +394,9 @@
     DEFINE_OP_CLASS_ID
     const char* name() const override { return "BezierQuadTestOp"; }
 
-    static sk_sp<GrDrawOp> Make(sk_sp<GrGeometryProcessor> gp, const SkRect& bounds, GrColor color,
-                                const GrPathUtils::QuadUVMatrix& devToUV) {
-        return sk_sp<GrDrawOp>(new BezierQuadTestOp(gp, bounds, color, devToUV));
+    static std::unique_ptr<GrDrawOp> Make(sk_sp<GrGeometryProcessor> gp, const SkRect& bounds,
+                                          GrColor color, const GrPathUtils::QuadUVMatrix& devToUV) {
+        return std::unique_ptr<GrDrawOp>(new BezierQuadTestOp(gp, bounds, color, devToUV));
     }
 
 private:
@@ -539,14 +540,15 @@
                     canvas->drawRect(bounds, boundsPaint);
 
                     GrPaint grPaint;
-                    grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+                    grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
 
                     GrPathUtils::QuadUVMatrix DevToUV(pts);
 
-                    sk_sp<GrDrawOp> op = BezierQuadTestOp::Make(gp, bounds, color, DevToUV);
+                    std::unique_ptr<GrDrawOp> op =
+                            BezierQuadTestOp::Make(gp, bounds, color, DevToUV);
 
-                    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                      std::move(op));
+                    renderTargetContext->priv().testingOnly_addDrawOp(
+                            std::move(grPaint), GrAAType::kNone, std::move(op));
                 }
                 ++col;
                 if (numCols == col) {
diff --git a/gm/bigrrectaaeffect.cpp b/gm/bigrrectaaeffect.cpp
index 372858e..f4e00fb 100644
--- a/gm/bigrrectaaeffect.cpp
+++ b/gm/bigrrectaaeffect.cpp
@@ -74,23 +74,22 @@
                 paint.setColor(SK_ColorWHITE);
                 canvas->drawRect(testBounds, paint);
 
-                GrPaint grPaint;
-                grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
-
                 SkRRect rrect = fRRect;
                 rrect.offset(SkIntToScalar(x + kGap), SkIntToScalar(y + kGap));
                 sk_sp<GrFragmentProcessor> fp(GrRRectEffect::Make(edgeType, rrect));
                 SkASSERT(fp);
                 if (fp) {
+                    GrPaint grPaint;
+                    grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
                     grPaint.addCoverageFragmentProcessor(std::move(fp));
 
                     SkRect bounds = testBounds;
                     bounds.offset(SkIntToScalar(x), SkIntToScalar(y));
 
-                    sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(0xff000000, SkMatrix::I(),
-                                                                      bounds, nullptr, nullptr));
-                    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                      std::move(op));
+                    std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
+                            0xff000000, SkMatrix::I(), bounds, nullptr, nullptr));
+                    renderTargetContext->priv().testingOnly_addDrawOp(
+                            std::move(grPaint), GrAAType::kNone, std::move(op));
                 }
             canvas->restore();
             x = x + fTestOffsetX;
diff --git a/gm/codec_scaled.cpp b/gm/codec_scaled.cpp
index 270de5d..a58f93c 100644
--- a/gm/codec_scaled.cpp
+++ b/gm/codec_scaled.cpp
@@ -79,13 +79,10 @@
             return;
         }
 
-        SkPMColor colorStorage[256];
-        int colorCount = 256;
-        sk_sp<SkColorTable> ctable(new SkColorTable(colorStorage, colorCount));
-
         SkAutoCanvasRestore acr(canvas, true);
         for (float scale : { 1.0f, .875f, .750f, .625f, .5f, .375f, .25f, .125f }) {
-            const auto info = fGenerator->getInfo();
+            // generateScaledPixels does not support index8
+            const auto info = fGenerator->getInfo().makeColorType(kN32_SkColorType);
             auto scaledInfo = info;
             SkImageGenerator::SupportedSizes sizes;
             if (fGenerator->computeScaledDimensions(scale, &sizes)) {
@@ -93,23 +90,14 @@
             }
 
             SkBitmap bm;
-            bm.setInfo(scaledInfo);
-            SkColorTable* ctablePtr = info.colorType() == kIndex_8_SkColorType ? ctable.get()
-                                                                               : nullptr;
-            bm.allocPixels(ctablePtr);
-            SkPixmap pixmap(scaledInfo, bm.getPixels(), bm.rowBytes(), ctablePtr);
+            bm.allocPixels(scaledInfo);
+            SkPixmap pixmap(scaledInfo, bm.getPixels(), bm.rowBytes());
             if (fGenerator->generateScaledPixels(pixmap)) {
-                // If there's a color table, we need to use it.
-                SkBitmap bm2;
-                bm2.installPixels(pixmap);
-                canvas->drawBitmap(bm2, 0, 0);
+                canvas->drawBitmap(bm, 0, 0);
             }
 
-            bm.setInfo(info);
-            bm.allocPixels(ctablePtr);
-            colorCount = 256;
-            if (fGenerator->getPixels(info, bm.getPixels(), bm.rowBytes(),
-                    const_cast<SkPMColor*>(ctable->readColors()), &colorCount)) {
+            bm.allocPixels(info);
+            if (fGenerator->getPixels(info, bm.getPixels(), bm.rowBytes())) {
                 SkAutoCanvasRestore acr2(canvas, true);
                 canvas->translate(0, SkIntToScalar(info.height()));
                 canvas->scale(SkFloatToScalar(scale), SkFloatToScalar(scale));
diff --git a/gm/concavepaths.cpp b/gm/concavepaths.cpp
index ad87d25..c68265b 100644
--- a/gm/concavepaths.cpp
+++ b/gm/concavepaths.cpp
@@ -80,15 +80,18 @@
     canvas->restore();
 }
 
-// Collinear edges
-void test_collinear_edges(SkCanvas* canvas, const SkPaint& paint) {
+// Overlapping "Fast-forward" icon: tests coincidence of inner and outer
+// vertices generated by intersection.
+void test_fast_forward(SkCanvas* canvas, const SkPaint& paint) {
     SkPath path;
     canvas->save();
     canvas->translate(100, 100);
     path.moveTo(SkIntToScalar(20), SkIntToScalar(20));
-    path.lineTo(SkIntToScalar(50), SkIntToScalar(20));
-    path.lineTo(SkIntToScalar(80), SkIntToScalar(20));
-    path.lineTo(SkIntToScalar(50), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(60), SkIntToScalar(50));
+    path.lineTo(SkIntToScalar(20), SkIntToScalar(80));
+    path.moveTo(SkIntToScalar(40), SkIntToScalar(20));
+    path.lineTo(SkIntToScalar(40), SkIntToScalar(80));
+    path.lineTo(SkIntToScalar(80), SkIntToScalar(50));
     canvas->drawPath(path, paint);
     canvas->restore();
 }
@@ -384,7 +387,7 @@
         test_bowtie(canvas, paint);
         test_fake_bowtie(canvas, paint);
         test_fish(canvas, paint);
-        test_collinear_edges(canvas, paint);
+        test_fast_forward(canvas, paint);
         test_hole(canvas, paint);
         test_star(canvas, paint);
         test_stairstep(canvas, paint);
diff --git a/gm/constcolorprocessor.cpp b/gm/constcolorprocessor.cpp
index 0e3c56f..17a1bc5 100644
--- a/gm/constcolorprocessor.cpp
+++ b/gm/constcolorprocessor.cpp
@@ -109,10 +109,10 @@
 
                     grPaint.addColorFragmentProcessor(std::move(fp));
 
-                    sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
+                    std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
                             grPaint.getColor(), viewMatrix, renderRect, nullptr, nullptr));
-                    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                      std::move(op));
+                    renderTargetContext->priv().testingOnly_addDrawOp(
+                            std::move(grPaint), GrAAType::kNone, std::move(op));
 
                     // 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.
diff --git a/gm/convexpolyeffect.cpp b/gm/convexpolyeffect.cpp
index d6decb9..1be92da 100644
--- a/gm/convexpolyeffect.cpp
+++ b/gm/convexpolyeffect.cpp
@@ -46,8 +46,8 @@
 
     const char* name() const override { return "PolyBoundsOp"; }
 
-    static sk_sp<GrDrawOp> Make(const SkRect& rect, GrColor color) {
-        return sk_sp<GrDrawOp>(new PolyBoundsOp(rect, color));
+    static std::unique_ptr<GrDrawOp> Make(const SkRect& rect, GrColor color) {
+        return std::unique_ptr<GrDrawOp>(new PolyBoundsOp(rect, color));
     }
 
 private:
@@ -58,10 +58,8 @@
         using namespace GrDefaultGeoProcFactory;
 
         Color color(this->color());
-        Coverage coverage(Coverage::kSolid_Type);
-        LocalCoords localCoords(LocalCoords::kUnused_Type);
-        sk_sp<GrGeometryProcessor> gp(
-            GrDefaultGeoProcFactory::Make(color, coverage, localCoords, SkMatrix::I()));
+        sk_sp<GrGeometryProcessor> gp(GrDefaultGeoProcFactory::Make(
+                color, Coverage::kSolid_Type, LocalCoords::kUnused_Type, SkMatrix::I()));
 
         size_t vertexStride = gp->getVertexStride();
         SkASSERT(vertexStride == sizeof(SkPoint));
@@ -182,13 +180,13 @@
                 }
 
                 GrPaint grPaint;
-                grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+                grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
                 grPaint.addCoverageFragmentProcessor(std::move(fp));
 
-                sk_sp<GrDrawOp> op = PolyBoundsOp::Make(p.getBounds(), 0xff000000);
+                std::unique_ptr<GrDrawOp> op = PolyBoundsOp::Make(p.getBounds(), 0xff000000);
 
-                renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                  std::move(op));
+                renderTargetContext->priv().testingOnly_addDrawOp(std::move(grPaint),
+                                                                  GrAAType::kNone, std::move(op));
 
                 x += SkScalarCeilToScalar(path->getBounds().width() + kDX);
             }
@@ -222,13 +220,13 @@
                 }
 
                 GrPaint grPaint;
-                grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+                grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
                 grPaint.addCoverageFragmentProcessor(std::move(fp));
 
-                sk_sp<GrDrawOp> op = PolyBoundsOp::Make(rect, 0xff000000);
+                std::unique_ptr<GrDrawOp> op = PolyBoundsOp::Make(rect, 0xff000000);
 
-                renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                  std::move(op));
+                renderTargetContext->priv().testingOnly_addDrawOp(std::move(grPaint),
+                                                                  GrAAType::kNone, std::move(op));
 
                 x += SkScalarCeilToScalar(rect.width() + kDX);
             }
diff --git a/gm/croppedrects.cpp b/gm/croppedrects.cpp
index ce7ff4b..2d7de95 100644
--- a/gm/croppedrects.cpp
+++ b/gm/croppedrects.cpp
@@ -46,7 +46,7 @@
         stroke.setColor(0xff008800);
         srcCanvas->drawRect(kSrcImageClip.makeInset(kStrokeWidth / 2, kStrokeWidth / 2), stroke);
 
-        fSrcImage = srcSurface->makeImageSnapshot(SkBudgeted::kYes, SkSurface::kNo_ForceUnique);
+        fSrcImage = srcSurface->makeImageSnapshot(SkBudgeted::kYes);
         fSrcImageShader = fSrcImage->makeShader(SkShader::kClamp_TileMode,
                                                 SkShader::kClamp_TileMode);
     }
diff --git a/gm/cubicpaths.cpp b/gm/cubicpaths.cpp
index abac5b2..023316b 100644
--- a/gm/cubicpaths.cpp
+++ b/gm/cubicpaths.cpp
@@ -426,6 +426,30 @@
     canvas->drawPath(path, p);
 }
 
+DEF_SIMPLE_GM(bug6083, canvas, 100, 50) {
+    SkPaint p;

+    p.setColor(SK_ColorRED);

+    p.setAntiAlias(true);

+    p.setStyle(SkPaint::kStroke_Style);

+    p.setStrokeWidth(15);

+    canvas->translate(-500, -130);

+    SkPath path;

+    path.moveTo(500.988f, 155.200f);

+    path.lineTo(526.109f, 155.200f);

+    SkPoint p1 = { 526.109f, 155.200f };

+    SkPoint p2 = { 525.968f, 212.968f };

+    SkPoint p3 = { 526.109f, 241.840f };

+    path.cubicTo(p1, p2, p3);

+    canvas->drawPath(path, p);

+    canvas->translate(50, 0);

+    path.reset();

+    p2.set(525.968f, 213.172f);

+    path.moveTo(500.988f, 155.200f);

+    path.lineTo(526.109f, 155.200f);

+    path.cubicTo(p1, p2, p3);

+    canvas->drawPath(path, p);

+}
+
 //////////////////////////////////////////////////////////////////////////////
 
 DEF_GM( return new CubicPathGM; )
diff --git a/gm/drawatlas.cpp b/gm/drawatlas.cpp
index c098e15..2526177 100644
--- a/gm/drawatlas.cpp
+++ b/gm/drawatlas.cpp
@@ -6,6 +6,8 @@
  */
 
 #include "gm.h"
+
+#include "SkAutoMalloc.h"
 #include "SkCanvas.h"
 #include "SkRSXform.h"
 #include "SkSurface.h"
diff --git a/gm/encode-platform.cpp b/gm/encode-platform.cpp
index e8f420f..ca3cda1 100644
--- a/gm/encode-platform.cpp
+++ b/gm/encode-platform.cpp
@@ -70,7 +70,8 @@
     #else
         switch (type) {
             case SkEncodedImageFormat::kPNG:
-                return SkEncodeImageAsPNG(&buf, src) ? buf.detachAsData() : nullptr;
+                return SkEncodeImageAsPNG(&buf, src, SkEncodeOptions()) ? buf.detachAsData()
+                                                                        : nullptr;
             case SkEncodedImageFormat::kJPEG:
                 return SkEncodeImageAsJPEG(&buf, src, 100) ? buf.detachAsData() : nullptr;
             case SkEncodedImageFormat::kWEBP:
diff --git a/gm/encode-srgb.cpp b/gm/encode-srgb.cpp
new file mode 100644
index 0000000..b9fe65b
--- /dev/null
+++ b/gm/encode-srgb.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+#include "Resources.h"
+#include "SkCanvas.h"
+#include "SkCodec.h"
+#include "SkData.h"
+#include "SkImageEncoderPriv.h"
+#include "SkPM4f.h"
+#include "SkSRGB.h"
+
+namespace skiagm {
+
+static const int imageWidth = 128;
+static const int imageHeight = 128;
+
+static inline int div_round_up(int a, int b) {
+    return (a + b - 1) / b;
+}
+
+static void make_index8(SkBitmap* bitmap, SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace) {
+    const SkColor colors[] = {
+            0x800000FF, 0x8000FF00, 0x80FF0000, 0x80FFFF00,
+    };
+
+    auto toPMColor = [alphaType, colorSpace](SkColor color) {
+        // In the unpremul case, just convert to SkPMColor ordering.
+        if (kUnpremul_SkAlphaType == alphaType) {
+            return SkSwizzle_BGRA_to_PMColor(color);
+        }
+
+        // Linear premultiply.
+        if (colorSpace) {
+            uint32_t result;
+            Sk4f pmFloat = SkColor4f::FromColor(color).premul().to4f_pmorder();
+            SkNx_cast<uint8_t>(sk_linear_to_srgb_needs_trunc(pmFloat)).store(&result);
+            result = (result & 0x00FFFFFF) | (color & 0xFF000000);
+            return result;
+        }
+
+        // Legacy premultiply.
+        return SkPreMultiplyColor(color);
+    };
+
+    // Note that these are not necessarily premultiplied, but they are platform byte ordering.
+    SkPMColor pmColors[SK_ARRAY_COUNT(colors)];
+    for (int i = 0; i < (int) SK_ARRAY_COUNT(colors); i++) {
+        pmColors[i] = toPMColor(colors[i]);
+    }
+
+    sk_sp<SkColorTable> colorTable(new SkColorTable(pmColors, SK_ARRAY_COUNT(pmColors)));
+    SkImageInfo info = SkImageInfo::Make(imageWidth, imageHeight, kIndex_8_SkColorType,
+                                         alphaType, colorSpace);
+    bitmap->allocPixels(info, nullptr, colorTable.get());
+    for (int y = 0; y < imageHeight; y++) {
+        for (int x = 0; x < imageWidth; x++) {
+            *bitmap->getAddr8(x, y) = (x / div_round_up(imageWidth, 2)) +
+                                      (y / div_round_up(imageHeight, 3));
+        }
+    }
+}
+
+static void make(SkBitmap* bitmap, SkColorType colorType, SkAlphaType alphaType,
+                 sk_sp<SkColorSpace> colorSpace) {
+    if (kIndex_8_SkColorType == colorType) {
+        make_index8(bitmap, alphaType, colorSpace);
+        return;
+    }
+
+    sk_sp<SkData> data = GetResourceAsData("color_wheel.png");
+    std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
+    SkImageInfo dstInfo = codec->getInfo().makeColorType(colorType)
+                                          .makeAlphaType(alphaType)
+                                          .makeColorSpace(colorSpace);
+    bitmap->allocPixels(dstInfo);
+    codec->getPixels(dstInfo, bitmap->getPixels(), bitmap->rowBytes());
+}
+
+static sk_sp<SkData> encode_data(const SkBitmap& bitmap) {
+    SkAutoLockPixels autoLockPixels(bitmap);
+    SkPixmap src;
+    if (!bitmap.peekPixels(&src)) {
+        return nullptr;
+    }
+    SkDynamicMemoryWStream buf;
+    SkEncodeOptions options;
+    if (bitmap.colorSpace()) {
+        options.fPremulBehavior = SkEncodeOptions::PremulBehavior::kGammaCorrect;
+    }
+    SkAssertResult(SkEncodeImageAsPNG(&buf, src, options));
+    return buf.detachAsData();
+}
+
+class EncodeSRGBGM : public GM {
+public:
+    EncodeSRGBGM() {}
+
+protected:
+    SkString onShortName() override {
+        return SkString("encode-srgb");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(imageWidth * 2, imageHeight * 4);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        const SkColorType colorTypes[] = { kN32_SkColorType, kIndex_8_SkColorType, };
+        const SkAlphaType alphaTypes[] = { kUnpremul_SkAlphaType, kPremul_SkAlphaType, };
+        const sk_sp<SkColorSpace> colorSpaces[] = {
+                nullptr, SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named),
+        };
+
+        SkBitmap bitmap;
+        for (SkColorType colorType : colorTypes) {
+            for (SkAlphaType alphaType : alphaTypes) {
+                canvas->save();
+                for (sk_sp<SkColorSpace> colorSpace : colorSpaces) {
+                    make(&bitmap, colorType, alphaType, colorSpace);
+                    auto image = SkImage::MakeFromEncoded(encode_data(bitmap));
+                    canvas->drawImage(image.get(), 0.0f, 0.0f);
+                    canvas->translate((float) imageWidth, 0.0f);
+                }
+                canvas->restore();
+                canvas->translate(0.0f, (float) imageHeight);
+            }
+        }
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+DEF_GM( return new EncodeSRGBGM; )
+}
diff --git a/gm/fontmgr.cpp b/gm/fontmgr.cpp
index 216fdd8..cd596eb 100644
--- a/gm/fontmgr.cpp
+++ b/gm/fontmgr.cpp
@@ -55,15 +55,15 @@
 
 class FontMgrGM : public skiagm::GM {
 public:
-    FontMgrGM(SkFontMgr* fontMgr = nullptr) {
+    FontMgrGM(sk_sp<SkFontMgr> fontMgr = nullptr) {
         SkGraphics::SetFontCacheLimit(16 * 1024 * 1024);
 
         fName.set("fontmgr_iter");
         if (fontMgr) {
             fName.append("_factory");
-            fFM.reset(fontMgr);
+            fFM = std::move(fontMgr);
         } else {
-            fFM.reset(SkFontMgr::RefDefault());
+            fFM = SkFontMgr::RefDefault();
         }
         fName.append(sk_tool_utils::platform_os_name());
         fName.append(sk_tool_utils::platform_extra_config("GDI"));
@@ -225,7 +225,7 @@
         }
         fName.append(sk_tool_utils::platform_os_name());
         fName.append(sk_tool_utils::platform_extra_config("GDI"));
-        fFM.reset(SkFontMgr::RefDefault());
+        fFM = SkFontMgr::RefDefault();
     }
 
     static void show_bounds(SkCanvas* canvas, const SkPaint& paint, SkScalar x, SkScalar y,
diff --git a/gm/gm.h b/gm/gm.h
index 0b26c16..2e4b602 100644
--- a/gm/gm.h
+++ b/gm/gm.h
@@ -13,7 +13,7 @@
 #include "SkPaint.h"
 #include "SkSize.h"
 #include "SkString.h"
-#include "SkTRegistry.h"
+#include "../tools/Registry.h"
 #include "sk_tool_utils.h"
 #include "SkClipOpPriv.h"
 
@@ -130,7 +130,7 @@
         SkMatrix fStarterMatrix;
     };
 
-    typedef SkTRegistry<GM*(*)(void*)> GMRegistry;
+    typedef sk_tools::Registry<GM*(*)(void*)> GMRegistry;
 
     class SimpleGM : public skiagm::GM {
     public:
diff --git a/gm/image.cpp b/gm/image.cpp
index 0bdd994..595a5ef 100644
--- a/gm/image.cpp
+++ b/gm/image.cpp
@@ -246,7 +246,7 @@
     SkPictureRecorder recorder;
     draw(recorder.beginRecording(SkRect::MakeIWH(info.width(), info.height())));
     return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(),
-                                    info.dimensions(), nullptr, nullptr,
+                                    info.dimensions(), nullptr, nullptr, SkImage::BitDepth::kU8,
                                     SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
 }
 
@@ -345,6 +345,7 @@
     draw_opaque_contents(recorder.beginRecording(SkRect::MakeIWH(info.width(), info.height())));
     sk_sp<SkPicture> pict(recorder.finishRecordingAsPicture());
     return SkImageGenerator::NewFromPicture(info.dimensions(), pict.get(), nullptr, nullptr,
+                                            SkImage::BitDepth::kU8,
                                             SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
 }
 
diff --git a/gm/image_pict.cpp b/gm/image_pict.cpp
index 229f3c5..1fdff21 100644
--- a/gm/image_pict.cpp
+++ b/gm/image_pict.cpp
@@ -66,11 +66,13 @@
 
         SkMatrix matrix;
         matrix.setTranslate(-100, -100);
-        fImage0 = SkImage::MakeFromPicture(fPicture, size, &matrix, nullptr, srgbColorSpace);
+        fImage0 = SkImage::MakeFromPicture(fPicture, size, &matrix, nullptr,
+                                           SkImage::BitDepth::kU8, srgbColorSpace);
         matrix.postTranslate(-50, -50);
         matrix.postRotate(45);
         matrix.postTranslate(50, 50);
-        fImage1 = SkImage::MakeFromPicture(fPicture, size, &matrix, nullptr, srgbColorSpace);
+        fImage1 = SkImage::MakeFromPicture(fPicture, size, &matrix, nullptr,
+                                           SkImage::BitDepth::kU8, srgbColorSpace);
     }
 
     void drawSet(SkCanvas* canvas) const {
@@ -109,6 +111,7 @@
     SkMatrix matrix;
     matrix.setTranslate(-100, -100);
     return SkImageGenerator::NewFromPicture(SkISize::Make(100, 100), pic, &matrix, nullptr,
+                                            SkImage::BitDepth::kU8,
                                             SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
 }
 
diff --git a/gm/image_shader.cpp b/gm/image_shader.cpp
index 7803739..522d373 100644
--- a/gm/image_shader.cpp
+++ b/gm/image_shader.cpp
@@ -48,6 +48,7 @@
 
 static sk_sp<SkImage> make_pict_gen(GrContext*, SkPicture* pic, const SkImageInfo& info) {
     return SkImage::MakeFromPicture(sk_ref_sp(pic), info.dimensions(), nullptr, nullptr,
+                                    SkImage::BitDepth::kU8,
                                     SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
 }
 
diff --git a/gm/imagefilters.cpp b/gm/imagefilters.cpp
index 27422b5..8855afd 100644
--- a/gm/imagefilters.cpp
+++ b/gm/imagefilters.cpp
@@ -68,7 +68,7 @@
 }
 
 static sk_sp<SkImage> make_image(SkCanvas* canvas) {
-    const SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
+    const SkImageInfo info = SkImageInfo::MakeS32(100, 100, kPremul_SkAlphaType);
     auto surface(canvas->makeSurface(info));
     if (!surface) {
         surface = SkSurface::MakeRaster(info);
diff --git a/gm/imagefiltersgraph.cpp b/gm/imagefiltersgraph.cpp
index fd4d5cf..30c987a 100644
--- a/gm/imagefiltersgraph.cpp
+++ b/gm/imagefiltersgraph.cpp
@@ -7,6 +7,7 @@
 
 #include "gm.h"
 
+#include "SkArithmeticImageFilter.h"
 #include "SkBlurImageFilter.h"
 #include "SkColorFilter.h"
 #include "SkColorFilterImageFilter.h"
@@ -14,13 +15,13 @@
 #include "SkImage.h"
 #include "SkImageSource.h"
 #include "SkMatrixConvolutionImageFilter.h"
+#include "SkMergeImageFilter.h"
+#include "SkMorphologyImageFilter.h"
 #include "SkOffsetImageFilter.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
 #include "SkSpecialSurface.h"
 #include "SkWriteBuffer.h"
-#include "SkMergeImageFilter.h"
-#include "SkMorphologyImageFilter.h"
 #include "SkXfermodeImageFilter.h"
 
 class ImageFiltersGraphGM : public skiagm::GM {
@@ -88,11 +89,8 @@
                                                                         matrixFilter));
 
             SkPaint paint;
-            paint.setImageFilter(
-                SkXfermodeImageFilter::MakeArithmetic(0, 1, 1, 0, true,
-                                            std::move(matrixFilter),
-                                            std::move(offsetFilter),
-                                            nullptr));
+            paint.setImageFilter(SkArithmeticImageFilter::Make(
+                    0, 1, 1, 0, true, std::move(matrixFilter), std::move(offsetFilter), nullptr));
 
             DrawClippedImage(canvas, fImage.get(), paint);
             canvas->translate(SkIntToScalar(100), 0);
diff --git a/gm/matrixconvolution.cpp b/gm/matrixconvolution.cpp
index 153f7a5..ce5ce52 100644
--- a/gm/matrixconvolution.cpp
+++ b/gm/matrixconvolution.cpp
@@ -7,26 +7,32 @@
 
 #include "gm.h"
 #include "SkColor.h"
-#include "SkMatrixConvolutionImageFilter.h"
 #include "SkGradientShader.h"
+#include "SkMatrixConvolutionImageFilter.h"
+#include "SkPixelRef.h"
 
 namespace skiagm {
 
 class MatrixConvolutionGM : public GM {
 public:
-    MatrixConvolutionGM() {
+    MatrixConvolutionGM(SkColor colorOne, SkColor colorTwo, const char* nameSuffix)
+            : fNameSuffix(nameSuffix) {
         this->setBGColor(0x00000000);
+        fColors[0] = colorOne;
+        fColors[1] = colorTwo;
     }
 
 protected:
 
     SkString onShortName() override {
-        return SkString("matrixconvolution");
+        return SkStringPrintf("matrixconvolution%s", fNameSuffix);
     }
 
     void makeBitmap() {
-        fBitmap.allocN32Pixels(80, 80);
-        SkCanvas canvas(fBitmap);
+        // Draw our bitmap in N32, so legacy devices get "premul" values they understand
+        SkBitmap n32Bitmap;
+        n32Bitmap.allocN32Pixels(80, 80);
+        SkCanvas canvas(n32Bitmap);
         canvas.clear(0x00000000);
         SkPaint paint;
         paint.setAntiAlias(true);
@@ -35,12 +41,15 @@
         paint.setTextSize(SkIntToScalar(180));
         SkPoint pts[2] = { SkPoint::Make(0, 0),
                            SkPoint::Make(0, SkIntToScalar(80)) };
-        SkColor colors[2] = { 0xFFFFFFFF, 0x40404040 };
         SkScalar pos[2] = { 0, SkIntToScalar(80) };
         paint.setShader(SkGradientShader::MakeLinear(
-            pts, colors, pos, 2, SkShader::kClamp_TileMode));
+            pts, fColors, pos, 2, SkShader::kClamp_TileMode));
         const char* str = "e";
         canvas.drawText(str, strlen(str), SkIntToScalar(-10), SkIntToScalar(80), paint);
+
+        // ... tag the data as sRGB, so color-aware devices do gamut adjustment, etc...
+        fBitmap.setInfo(SkImageInfo::MakeS32(80, 80, kPremul_SkAlphaType));
+        fBitmap.setPixelRef(sk_ref_sp(n32Bitmap.pixelRef()), 0, 0);
     }
 
     SkISize onISize() override {
@@ -57,6 +66,19 @@
         };
         SkISize kernelSize = SkISize::Make(3, 3);
         SkScalar gain = 0.3f, bias = SkIntToScalar(100);
+        if (canvas->imageInfo().colorSpace()) {
+            // TODO: Gain and bias are poorly specified (in the feConvolveMatrix SVG documentation,
+            // there is obviously no mention of gamma or color spaces). Eventually, we need to
+            // decide what to do with these (they generally have an extreme brightening effect).
+            // For now, I'm modifying this GM to use values tuned to preserve luminance across the
+            // range of input values (compared to the legacy math and values).
+            //
+            // It's impossible to match the results exactly, because legacy math produces a flat
+            // response (when looking at sRGB encoded results), while gamma-correct math produces
+            // a curve.
+            gain = 0.25f;
+            bias = 16.5f;
+        }
         SkPaint paint;
         paint.setImageFilter(SkMatrixConvolutionImageFilter::Make(kernelSize,
                                                                   kernel,
@@ -104,12 +126,15 @@
 
 private:
     SkBitmap fBitmap;
+    SkColor fColors[2];
+    const char* fNameSuffix;
 
     typedef GM INHERITED;
 };
 
 //////////////////////////////////////////////////////////////////////////////
 
-DEF_GM(return new MatrixConvolutionGM;)
+DEF_GM(return new MatrixConvolutionGM(0xFFFFFFFF, 0x40404040, "");)
+DEF_GM(return new MatrixConvolutionGM(0xFFFF0000, 0xFF00FF00, "_color");)
 
 }
diff --git a/gm/pathfill.cpp b/gm/pathfill.cpp
index da0efea..2562e7c 100644
--- a/gm/pathfill.cpp
+++ b/gm/pathfill.cpp
@@ -140,6 +140,49 @@
     return SkIntToScalar(40);
 }
 
+static SkScalar make_info(SkPath* path) {
+    path->moveTo(24, 4);
+    path->cubicTo(12.94999980926514f,
+                  4,
+                  4,
+                  12.94999980926514f,
+                  4,
+                  24);
+    path->cubicTo(4,
+                  35.04999923706055f,
+                  12.94999980926514f,
+                  44,
+                  24,
+                  44);
+    path->cubicTo(35.04999923706055f,
+                  44,
+                  44,
+                  35.04999923706055f,
+                  44,
+                  24);
+    path->cubicTo(44,
+                  12.95000076293945f,
+                  35.04999923706055f,
+                  4,
+                  24,
+                  4);
+    path->close();
+    path->moveTo(26, 34);
+    path->lineTo(22, 34);
+    path->lineTo(22, 22);
+    path->lineTo(26, 22);
+    path->lineTo(26, 34);
+    path->close();
+    path->moveTo(26, 18);
+    path->lineTo(22, 18);
+    path->lineTo(22, 14);
+    path->lineTo(26, 14);
+    path->lineTo(26, 18);
+    path->close();
+
+    return SkIntToScalar(44);
+}
+
 constexpr MakePathProc gProcs[] = {
     make_frame,
     make_triangle,
@@ -158,11 +201,14 @@
 class PathFillGM : public skiagm::GM {
     SkPath  fPath[N];
     SkScalar fDY[N];
+    SkPath  fInfoPath;
 protected:
     void onOnceBeforeDraw() override {
         for (size_t i = 0; i < N; i++) {
             fDY[i] = gProcs[i](&fPath[i]);
         }
+
+        (void) make_info(&fInfoPath);
     }
 
 
@@ -182,6 +228,10 @@
             canvas->drawPath(fPath[i], paint);
             canvas->translate(SkIntToScalar(0), fDY[i]);
         }
+
+        canvas->scale(0.300000011920929f, 0.300000011920929f);
+        canvas->translate(50, 50);
+        canvas->drawPath(fInfoPath, paint);
     }
 
 private:
diff --git a/gm/pictureimagegenerator.cpp b/gm/pictureimagegenerator.cpp
index b247af2..accf072 100644
--- a/gm/pictureimagegenerator.cpp
+++ b/gm/pictureimagegenerator.cpp
@@ -156,7 +156,7 @@
             std::unique_ptr<SkImageGenerator> gen(
                 SkImageGenerator::NewFromPicture(configs[i].size, fPicture.get(), &m,
                                                  p.getAlpha() != 255 ? &p : nullptr,
-                                                 srgbColorSpace));
+                                                 SkImage::BitDepth::kU8, srgbColorSpace));
 
             SkImageInfo bmInfo = gen->getInfo().makeColorSpace(
                 sk_ref_sp(canvas->imageInfo().colorSpace()));
diff --git a/gm/readpixels.cpp b/gm/readpixels.cpp
new file mode 100644
index 0000000..df7fb07
--- /dev/null
+++ b/gm/readpixels.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "Resources.h"
+#include "SkCodec.h"
+#include "SkColorSpace.h"
+#include "SkColorSpace_Base.h"
+#include "SkHalf.h"
+#include "SkImage.h"
+#include "SkPictureRecorder.h"
+
+static void clamp_if_necessary(const SkImageInfo& info, void* pixels) {
+    if (kRGBA_F16_SkColorType != info.colorType()) {
+        return;
+    }
+
+    for (int y = 0; y < info.height(); y++) {
+        for (int x = 0; x < info.width(); x++) {
+            uint64_t pixel = ((uint64_t*) pixels)[y * info.width() + x];
+
+            Sk4f rgba = SkHalfToFloat_finite_ftz(pixel);
+            if (kUnpremul_SkAlphaType == info.alphaType()) {
+                rgba = Sk4f::Max(0.0f, Sk4f::Min(rgba, 1.0f));
+            } else {
+                SkASSERT(kPremul_SkAlphaType == info.alphaType());
+                rgba = Sk4f::Max(0.0f, Sk4f::Min(rgba, rgba[3]));
+            }
+            SkFloatToHalf_finite_ftz(rgba).store(&pixel);
+
+            ((uint64_t*) pixels)[y * info.width() + x] = pixel;
+        }
+    }
+}
+
+sk_sp<SkColorSpace> fix_for_colortype(SkColorSpace* colorSpace, SkColorType colorType) {
+    if (kRGBA_F16_SkColorType == colorType) {
+        return as_CSB(colorSpace)->makeLinearGamma();
+    }
+
+    return sk_ref_sp(colorSpace);
+}
+
+static const int kWidth = 64;
+static const int kHeight = 64;
+
+static sk_sp<SkImage> make_raster_image(SkColorType colorType, SkAlphaType alphaType) {
+    std::unique_ptr<SkStream> stream(GetResourceAsStream("google_chrome.ico"));
+    std::unique_ptr<SkCodec> codec(SkCodec::NewFromStream(stream.release()));
+
+    SkBitmap bitmap;
+    SkImageInfo info = codec->getInfo().makeWH(kWidth, kHeight)
+                                       .makeColorType(colorType)
+                                       .makeAlphaType(alphaType)
+            .makeColorSpace(fix_for_colortype(codec->getInfo().colorSpace(), colorType));
+    bitmap.allocPixels(info);
+    codec->getPixels(info, bitmap.getPixels(), bitmap.rowBytes());
+    bitmap.setImmutable();
+    return SkImage::MakeFromBitmap(bitmap);
+}
+
+static sk_sp<SkImage> make_codec_image() {
+    sk_sp<SkData> encoded = GetResourceAsData("randPixels.png");
+    return SkImage::MakeFromEncoded(encoded);
+}
+
+static void draw_contents(SkCanvas* canvas) {
+    SkPaint paint;
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(20);
+    paint.setColor(0xFF800000);
+    canvas->drawCircle(40, 40, 35, paint);
+    paint.setColor(0xFF008000);
+    canvas->drawCircle(50, 50, 35, paint);
+    paint.setColor(0xFF000080);
+    canvas->drawCircle(60, 60, 35, paint);
+}
+
+static sk_sp<SkImage> make_tagged_picture_image() {
+    SkPictureRecorder recorder;
+    draw_contents(recorder.beginRecording(SkRect::MakeIWH(kWidth, kHeight)));
+    return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(),
+                                    SkISize::Make(kWidth, kHeight), nullptr, nullptr,
+                                    SkImage::BitDepth::kU8,
+                                    SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
+}
+
+static sk_sp<SkImage> make_untagged_picture_image() {
+    SkPictureRecorder recorder;
+    draw_contents(recorder.beginRecording(SkRect::MakeIWH(kWidth, kHeight)));
+    return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(),
+                                    SkISize::Make(kWidth, kHeight), nullptr, nullptr);
+}
+
+static sk_sp<SkColorSpace> make_srgb_transfer_fn(const SkColorSpacePrimaries& primaries) {
+    SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
+    SkAssertResult(primaries.toXYZD50(&toXYZD50));
+    return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, toXYZD50);
+}
+
+static sk_sp<SkColorSpace> make_wide_gamut() {
+    // ProPhoto
+    SkColorSpacePrimaries primaries;
+    primaries.fRX = 0.7347f;
+    primaries.fRY = 0.2653f;
+    primaries.fGX = 0.1596f;
+    primaries.fGY = 0.8404f;
+    primaries.fBX = 0.0366f;
+    primaries.fBY = 0.0001f;
+    primaries.fWX = 0.34567f;
+    primaries.fWY = 0.35850f;
+    return make_srgb_transfer_fn(primaries);
+}
+
+static sk_sp<SkColorSpace> make_small_gamut() {
+    SkColorSpacePrimaries primaries;
+    primaries.fRX = 0.50f;
+    primaries.fRY = 0.33f;
+    primaries.fGX = 0.30f;
+    primaries.fGY = 0.50f;
+    primaries.fBX = 0.25f;
+    primaries.fBY = 0.16f;
+    primaries.fWX = 0.3127f;
+    primaries.fWY = 0.3290f;
+    return make_srgb_transfer_fn(primaries);
+}
+
+static void draw_image(SkCanvas* canvas, SkImage* image, SkColorType dstColorType,
+                       SkAlphaType dstAlphaType, sk_sp<SkColorSpace> dstColorSpace,
+                       SkImage::CachingHint hint) {
+    size_t rowBytes = image->width() * SkColorTypeBytesPerPixel(dstColorType);
+    sk_sp<SkData> data = SkData::MakeUninitialized(rowBytes * image->height());
+    dstColorSpace = fix_for_colortype(dstColorSpace.get(), dstColorType);
+    SkImageInfo dstInfo = SkImageInfo::Make(image->width(), image->height(), dstColorType,
+                                            dstAlphaType, dstColorSpace);
+    image->readPixels(dstInfo, data->writable_data(), rowBytes, 0, 0, hint);
+
+    // readPixels() does not always clamp F16.  The drawing code expects pixels in the 0-1 range.
+    clamp_if_necessary(dstInfo, data->writable_data());
+
+    // Now that we have called readPixels(), dump the raw pixels into an srgb image.
+    sk_sp<SkColorSpace> srgb = fix_for_colortype(
+            SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named).get(), dstColorType);
+    sk_sp<SkImage> raw = SkImage::MakeRasterData(dstInfo.makeColorSpace(srgb), data, rowBytes);
+    canvas->drawImage(raw.get(), 0.0f, 0.0f, nullptr);
+}
+
+class ReadPixelsGM : public skiagm::GM {
+public:
+    ReadPixelsGM() {}
+
+protected:
+    SkString onShortName() override {
+        return SkString("readpixels");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(6 * kWidth, 18 * kHeight);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        if (!canvas->imageInfo().colorSpace()) {
+            // This gm is only interesting in color correct modes.
+            return;
+        }
+
+        const SkAlphaType alphaTypes[] = {
+                kUnpremul_SkAlphaType,
+                kPremul_SkAlphaType,
+        };
+        const SkColorType colorTypes[] = {
+                kRGBA_8888_SkColorType,
+                kBGRA_8888_SkColorType,
+                kRGBA_F16_SkColorType,
+        };
+        const sk_sp<SkColorSpace> colorSpaces[] = {
+                make_wide_gamut(),
+                SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named),
+                make_small_gamut(),
+        };
+
+        for (sk_sp<SkColorSpace> dstColorSpace : colorSpaces) {
+            for (SkColorType srcColorType : colorTypes) {
+                for (SkAlphaType srcAlphaType : alphaTypes) {
+                    canvas->save();
+                    sk_sp<SkImage> image = make_raster_image(srcColorType, srcAlphaType);
+                    for (SkColorType dstColorType : colorTypes) {
+                        for (SkAlphaType dstAlphaType : alphaTypes) {
+                            draw_image(canvas, image.get(), dstColorType, dstAlphaType,
+                                       dstColorSpace, SkImage::kAllow_CachingHint);
+                            canvas->translate((float) kWidth, 0.0f);
+                        }
+                    }
+                    canvas->restore();
+                    canvas->translate(0.0f, (float) kHeight);
+                }
+            }
+        }
+    }
+
+private:
+    typedef skiagm::GM INHERITED;
+};
+DEF_GM( return new ReadPixelsGM; )
+
+class ReadPixelsCodecGM : public skiagm::GM {
+public:
+    ReadPixelsCodecGM() {}
+
+protected:
+    SkString onShortName() override {
+        return SkString("readpixelscodec");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(3 * (kEncodedWidth + 1), 12 * (kEncodedHeight + 1));
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        if (!canvas->imageInfo().colorSpace()) {
+            // This gm is only interesting in color correct modes.
+            return;
+        }
+
+        const SkAlphaType alphaTypes[] = {
+                kUnpremul_SkAlphaType,
+                kPremul_SkAlphaType,
+        };
+        const SkColorType colorTypes[] = {
+                kRGBA_8888_SkColorType,
+                kBGRA_8888_SkColorType,
+                kRGBA_F16_SkColorType,
+        };
+        const sk_sp<SkColorSpace> colorSpaces[] = {
+                make_wide_gamut(),
+                SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named),
+                make_small_gamut(),
+        };
+        const SkImage::CachingHint hints[] = {
+                SkImage::kAllow_CachingHint,
+                SkImage::kDisallow_CachingHint,
+        };
+
+        sk_sp<SkImage> image = make_codec_image();
+        for (sk_sp<SkColorSpace> dstColorSpace : colorSpaces) {
+            canvas->save();
+            for (SkColorType dstColorType : colorTypes) {
+                for (SkAlphaType dstAlphaType : alphaTypes) {
+                    for (SkImage::CachingHint hint : hints) {
+                        draw_image(canvas, image.get(), dstColorType, dstAlphaType, dstColorSpace,
+                                   hint);
+                        canvas->translate(0.0f, (float) kEncodedHeight + 1);
+                    }
+                }
+            }
+            canvas->restore();
+            canvas->translate((float) kEncodedWidth + 1, 0.0f);
+        }
+    }
+
+private:
+    static const int kEncodedWidth = 8;
+    static const int kEncodedHeight = 8;
+
+    typedef skiagm::GM INHERITED;
+};
+DEF_GM( return new ReadPixelsCodecGM; )
+
+class ReadPixelsPictureGM : public skiagm::GM {
+public:
+    ReadPixelsPictureGM() {}
+
+protected:
+    SkString onShortName() override {
+        return SkString("readpixelspicture");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(3 * kWidth, 12 * kHeight);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        if (!canvas->imageInfo().colorSpace()) {
+            // This gm is only interesting in color correct modes.
+            return;
+        }
+
+        const sk_sp<SkImage> images[] = {
+                make_tagged_picture_image(),
+                make_untagged_picture_image(),
+        };
+        const SkAlphaType alphaTypes[] = {
+                kUnpremul_SkAlphaType,
+                kPremul_SkAlphaType,
+        };
+        const SkColorType colorTypes[] = {
+                kRGBA_8888_SkColorType,
+                kBGRA_8888_SkColorType,
+                kRGBA_F16_SkColorType,
+        };
+        const sk_sp<SkColorSpace> colorSpaces[] = {
+                make_wide_gamut(),
+                SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named),
+                make_small_gamut(),
+        };
+        const SkImage::CachingHint hints[] = {
+                SkImage::kAllow_CachingHint,
+                SkImage::kDisallow_CachingHint,
+        };
+
+        for (sk_sp<SkImage> image : images) {
+            for (sk_sp<SkColorSpace> dstColorSpace : colorSpaces) {
+                canvas->save();
+                for (SkColorType dstColorType : colorTypes) {
+                    for (SkAlphaType dstAlphaType : alphaTypes) {
+                        for (SkImage::CachingHint hint : hints) {
+                            draw_image(canvas, image.get(), dstColorType, dstAlphaType,
+                                       dstColorSpace, hint);
+                            canvas->translate(0.0f, (float) kHeight);
+                        }
+                    }
+                }
+                canvas->restore();
+                canvas->translate((float) kWidth, 0.0f);
+            }
+        }
+    }
+
+private:
+
+    typedef skiagm::GM INHERITED;
+};
+DEF_GM( return new ReadPixelsPictureGM; )
diff --git a/gm/rrects.cpp b/gm/rrects.cpp
index 4649a68..d4f5bdf 100644
--- a/gm/rrects.cpp
+++ b/gm/rrects.cpp
@@ -102,24 +102,22 @@
                     canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
                     if (kEffect_Type == fType) {
 #if SK_SUPPORT_GPU
-                        GrPaint grPaint;
-                        grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
-
                         SkRRect rrect = fRRects[curRRect];
                         rrect.offset(SkIntToScalar(x), SkIntToScalar(y));
                         GrPrimitiveEdgeType edgeType = (GrPrimitiveEdgeType) et;
                         sk_sp<GrFragmentProcessor> fp(GrRRectEffect::Make(edgeType, rrect));
                         if (fp) {
+                            GrPaint grPaint;
+                            grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
                             grPaint.addCoverageFragmentProcessor(std::move(fp));
 
                             SkRect bounds = rrect.getBounds();
                             bounds.outset(2.f, 2.f);
 
-                            sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
+                            std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
                                     0xff000000, SkMatrix::I(), bounds, nullptr, nullptr));
-                            renderTargetContext->priv().testingOnly_addDrawOp(grPaint,
-                                                                              GrAAType::kNone,
-                                                                              std::move(op));
+                            renderTargetContext->priv().testingOnly_addDrawOp(
+                                    std::move(grPaint), GrAAType::kNone, std::move(op));
                         } else {
                             drew = false;
                         }
diff --git a/gm/texdata.cpp b/gm/texdata.cpp
index 574dfad..8520d28 100644
--- a/gm/texdata.cpp
+++ b/gm/texdata.cpp
@@ -102,7 +102,8 @@
         tm.postIDiv(2*S, 2*S);
         paint.addColorTextureProcessor(texture, nullptr, tm);
 
-        renderTargetContext->drawRect(clip, paint, GrAA::kNo, vm, SkRect::MakeWH(2*S, 2*S));
+        renderTargetContext->drawRect(clip, GrPaint(paint), GrAA::kNo, vm,
+                                      SkRect::MakeWH(2 * S, 2 * S));
 
         // now update the lower right of the texture in first pass
         // or upper right in second pass
@@ -116,7 +117,8 @@
         texture->writePixels(S, (i ? 0 : S), S, S,
                                 texture->config(), gTextureData.get(),
                                 4 * stride);
-        renderTargetContext->drawRect(clip, paint, GrAA::kNo, vm, SkRect::MakeWH(2*S, 2*S));
+        renderTargetContext->drawRect(clip, std::move(paint), GrAA::kNo, vm,
+                                      SkRect::MakeWH(2 * S, 2 * S));
     }
 }
 #endif
diff --git a/gm/texturedomaineffect.cpp b/gm/texturedomaineffect.cpp
index cf7ec41..32042aa 100644
--- a/gm/texturedomaineffect.cpp
+++ b/gm/texturedomaineffect.cpp
@@ -113,12 +113,12 @@
                 for (int m = 0; m < GrTextureDomain::kModeCount; ++m) {
                     GrTextureDomain::Mode mode = (GrTextureDomain::Mode) m;
                     GrPaint grPaint;
-                    grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+                    grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
                     sk_sp<GrFragmentProcessor> fp(
-                        GrTextureDomainEffect::Make(texture.get(), nullptr, textureMatrices[tm],
-                                                GrTextureDomain::MakeTexelDomain(texture.get(),
-                                                                                 texelDomains[d]),
-                                                mode, GrSamplerParams::kNone_FilterMode));
+                        GrTextureDomainEffect::Make(
+                                   texture.get(), nullptr, textureMatrices[tm],
+                                   GrTextureDomain::MakeTexelDomainForMode(texelDomains[d], mode),
+                                   mode, GrSamplerParams::kNone_FilterMode));
 
                     if (!fp) {
                         continue;
@@ -126,10 +126,10 @@
                     const SkMatrix viewMatrix = SkMatrix::MakeTrans(x, y);
                     grPaint.addColorFragmentProcessor(std::move(fp));
 
-                    sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
+                    std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
                             GrColor_WHITE, viewMatrix, renderRect, nullptr, nullptr));
-                    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                      std::move(op));
+                    renderTargetContext->priv().testingOnly_addDrawOp(
+                            std::move(grPaint), GrAAType::kNone, std::move(op));
                     x += renderRect.width() + kTestPad;
                 }
                 y += renderRect.height() + kTestPad;
diff --git a/gm/thinconcavepaths.cpp b/gm/thinconcavepaths.cpp
new file mode 100644
index 0000000..6c81d7c
--- /dev/null
+++ b/gm/thinconcavepaths.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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 "gm.h"
+#include "SkCanvas.h"
+#include "SkPath.h"
+
+#define WIDTH 400
+#define HEIGHT 400
+
+namespace {
+// Test thin stroked rect (stroked "by hand", not by stroking).
+void draw_thin_stroked_rect(SkCanvas* canvas, const SkPaint& paint, SkScalar width) {
+    SkPath path;
+    path.moveTo(10 + width, 10 + width);
+    path.lineTo(40,         10 + width);
+    path.lineTo(40,         20);
+    path.lineTo(10 + width, 20);
+    path.moveTo(10,         10);
+    path.lineTo(10,         20 + width);
+    path.lineTo(40 + width, 20 + width);
+    path.lineTo(40 + width, 10);
+    canvas->drawPath(path, paint);
+}
+
+};
+
+class ThinConcavePathsGM : public skiagm::GM {
+public:
+    ThinConcavePathsGM() {}
+
+protected:
+    SkString onShortName() override {
+        return SkString("thinconcavepaths");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(WIDTH, HEIGHT);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        SkPaint paint;
+
+        paint.setAntiAlias(true);
+        paint.setStyle(SkPaint::kFill_Style);
+
+        canvas->save();
+        for (SkScalar width = 1.0; width < 2.05; width += 0.25) {
+            draw_thin_stroked_rect(canvas, paint, width);
+            canvas->translate(0, 25);
+        }
+        canvas->restore();
+    }
+
+private:
+    typedef skiagm::GM INHERITED;
+};
+
+DEF_GM( return new ThinConcavePathsGM; )
diff --git a/gm/verylargebitmap.cpp b/gm/verylargebitmap.cpp
index 280d117..449b229 100644
--- a/gm/verylargebitmap.cpp
+++ b/gm/verylargebitmap.cpp
@@ -33,6 +33,7 @@
     draw(recorder.beginRecording(SkRect::MakeIWH(width, height)), width, height, colors);
     return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(),
                                     SkISize::Make(width, height), nullptr, nullptr,
+                                    SkImage::BitDepth::kU8,
                                     SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
 }
 
diff --git a/gm/windowrectangles.cpp b/gm/windowrectangles.cpp
index e16392b..29356fc 100644
--- a/gm/windowrectangles.cpp
+++ b/gm/windowrectangles.cpp
@@ -75,7 +75,7 @@
 };
 
 /**
- * This is a simple helper class for resetting a canvas's clip to our test’s SkClipStack.
+ * This is a simple helper class for resetting a canvas's clip to our test's SkClipStack.
  */
 class ReplayClipStackVisitor final : public SkCanvasClipVisitor {
 public:
@@ -127,10 +127,8 @@
     constexpr static int kMaskCheckerSize = 5;
     SkString onShortName() final { return SkString("windowrectangles_mask"); }
     void onCoverClipStack(const SkClipStack&, SkCanvas*) final;
-    void visualizeAlphaMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&,
-                            const GrPaint&);
-    void visualizeStencilMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&,
-                              const GrPaint&);
+    void visualizeAlphaMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&, GrPaint&&);
+    void visualizeStencilMask(GrContext*, GrRenderTargetContext*, const GrReducedClip&, GrPaint&&);
     void stencilCheckerboard(GrRenderTargetContext*, bool flip);
     void fail(SkCanvas*);
 };
@@ -192,16 +190,15 @@
     GrPaint paint;
     if (!rtc->isStencilBufferMultisampled()) {
         paint.setColor4f(GrColor4f(0, 0.25f, 1, 1));
-        this->visualizeAlphaMask(ctx, rtc, reducedClip, paint);
+        this->visualizeAlphaMask(ctx, rtc, reducedClip, std::move(paint));
     } else {
         paint.setColor4f(GrColor4f(1, 0.25f, 0.25f, 1));
-        this->visualizeStencilMask(ctx, rtc, reducedClip, paint);
+        this->visualizeStencilMask(ctx, rtc, reducedClip, std::move(paint));
     }
 }
 
 void WindowRectanglesMaskGM::visualizeAlphaMask(GrContext* ctx, GrRenderTargetContext* rtc,
-                                                const GrReducedClip& reducedClip,
-                                                const GrPaint& paint) {
+                                                const GrReducedClip& reducedClip, GrPaint&& paint) {
     sk_sp<GrRenderTargetContext> maskRTC(
         ctx->makeRenderTargetContextWithFallback(SkBackingFit::kExact, kLayerRect.width(),
                                                  kLayerRect.height(), kAlpha_8_GrPixelConfig,
@@ -228,13 +225,13 @@
     // inside window rectangles or outside the scissor should still have the initial checkerboard
     // intact. (This verifies we didn't spend any time modifying those pixels in the mask.)
     AlphaOnlyClip clip(mask.get(), x, y);
-    rtc->drawRect(clip, paint, GrAA::kYes, SkMatrix::I(),
-                 SkRect::Make(SkIRect::MakeXYWH(x, y, mask->width(), mask->height())));
+    rtc->drawRect(clip, std::move(paint), GrAA::kYes, SkMatrix::I(),
+                  SkRect::Make(SkIRect::MakeXYWH(x, y, mask->width(), mask->height())));
 }
 
 void WindowRectanglesMaskGM::visualizeStencilMask(GrContext* ctx, GrRenderTargetContext* rtc,
                                                   const GrReducedClip& reducedClip,
-                                                  const GrPaint& paint) {
+                                                  GrPaint&& paint) {
     if (!ctx->resourceProvider()->attachStencilAttachment(rtc->accessRenderTarget())) {
         return;
     }
@@ -247,7 +244,7 @@
     // Now visualize the stencil mask by covering the entire render target. The regions inside
     // window rectangless or outside the scissor should still have the initial checkerboard intact.
     // (This verifies we didn't spend any time modifying those pixels in the mask.)
-    rtc->drawPaint(StencilOnlyClip(), paint, SkMatrix::I());
+    rtc->drawPaint(StencilOnlyClip(), std::move(paint), SkMatrix::I());
 }
 
 void WindowRectanglesMaskGM::stencilCheckerboard(GrRenderTargetContext* rtc, bool flip) {
diff --git a/gm/xfermodeimagefilter.cpp b/gm/xfermodeimagefilter.cpp
index 9190859..77c3502 100644
--- a/gm/xfermodeimagefilter.cpp
+++ b/gm/xfermodeimagefilter.cpp
@@ -7,6 +7,7 @@
 
 #include "gm.h"
 #include "sk_tool_utils.h"
+#include "SkArithmeticImageFilter.h"
 #include "SkImage.h"
 #include "SkImageSource.h"
 #include "SkOffsetImageFilter.h"
@@ -95,7 +96,7 @@
             }
         }
         // Test arithmetic mode as image filter
-        paint.setImageFilter(SkXfermodeImageFilter::MakeArithmetic(0, 1, 1, 0, true, background));
+        paint.setImageFilter(SkArithmeticImageFilter::Make(0, 1, 1, 0, true, background));
         DrawClippedBitmap(canvas, fBitmap, paint, x, y);
         x += fBitmap.width() + MARGIN;
         if (x + fBitmap.width() > WIDTH) {
diff --git a/gm/yuvtorgbeffect.cpp b/gm/yuvtorgbeffect.cpp
index 32c20d2..8d78f13 100644
--- a/gm/yuvtorgbeffect.cpp
+++ b/gm/yuvtorgbeffect.cpp
@@ -110,8 +110,6 @@
                                        {1, 2, 0}, {2, 0, 1}, {2, 1, 0}};
 
             for (int i = 0; i < 6; ++i) {
-                GrPaint grPaint;
-                grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
                 sk_sp<GrFragmentProcessor> fp(
                         GrYUVEffect::MakeYUVToRGB(texture[indices[i][0]].get(),
                                                   texture[indices[i][1]].get(),
@@ -120,13 +118,15 @@
                                                   static_cast<SkYUVColorSpace>(space),
                                                   false));
                 if (fp) {
+                    GrPaint grPaint;
+                    grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
+                    grPaint.addColorFragmentProcessor(std::move(fp));
                     SkMatrix viewMatrix;
                     viewMatrix.setTranslate(x, y);
-                    grPaint.addColorFragmentProcessor(std::move(fp));
-                    sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
+                    std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
                             GrColor_WHITE, viewMatrix, renderRect, nullptr, nullptr));
-                    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                      std::move(op));
+                    renderTargetContext->priv().testingOnly_addDrawOp(
+                            std::move(grPaint), GrAAType::kNone, std::move(op));
                 }
                 x += renderRect.width() + kTestPad;
             }
@@ -228,7 +228,7 @@
             SkScalar x = kDrawPad + kTestPad;
 
             GrPaint grPaint;
-            grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+            grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
             sk_sp<GrFragmentProcessor> fp(
                 GrYUVEffect::MakeYUVToRGB(texture[0].get(), texture[1].get(), texture[2].get(),
                                           sizes, static_cast<SkYUVColorSpace>(space), true));
@@ -236,10 +236,10 @@
                 SkMatrix viewMatrix;
                 viewMatrix.setTranslate(x, y);
                 grPaint.addColorFragmentProcessor(fp);
-                sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(GrColor_WHITE, viewMatrix,
-                                                                  renderRect, nullptr, nullptr));
-                renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
-                                                                  std::move(op));
+                std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
+                        GrColor_WHITE, viewMatrix, renderRect, nullptr, nullptr));
+                renderTargetContext->priv().testingOnly_addDrawOp(std::move(grPaint),
+                                                                  GrAAType::kNone, std::move(op));
             }
         }
     }
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index ef01130..97e5368 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -67,6 +67,7 @@
       "/FS",  # Preserve previous PDB behavior.
       "/bigobj",  # Some of our files are bigger than the regular limits.
       "/WX",  # Treat warnings as errors.
+      "/utf-8",  # Set Source and Executable character sets to UTF-8.
     ]
     defines += [
       "_CRT_SECURE_NO_WARNINGS",  # Disables warnings about sscanf().
@@ -81,11 +82,13 @@
       "$windk/../Windows Kits/10/Include/10.0.10150.0/ucrt",
       "$windk/../Windows Kits/8.1/Include/shared",
       "$windk/../Windows Kits/8.1/Include/um",
+      "$windk/../Windows Kits/8.1/Include/winrt",
 
       # For builds using win_toolchain asset.
-      "$windk/win_sdk/Include/10.0.10586.0/shared",
-      "$windk/win_sdk/Include/10.0.10586.0/ucrt",
-      "$windk/win_sdk/Include/10.0.10586.0/um",
+      "$windk/win_sdk/Include/10.0.14393.0/shared",
+      "$windk/win_sdk/Include/10.0.14393.0/ucrt",
+      "$windk/win_sdk/Include/10.0.14393.0/um",
+      "$windk/win_sdk/Include/10.0.14393.0/winrt",
     ]
     lib_dirs = [
       # For local builds.
@@ -93,8 +96,8 @@
       "$windk/../Windows Kits/8.1/Lib/winv6.3/um/$target_cpu",
 
       # For builds using win_toolchain asset.
-      "$windk/win_sdk/Lib/10.0.10586.0/ucrt/$target_cpu",
-      "$windk/win_sdk/Lib/10.0.10586.0/um/$target_cpu",
+      "$windk/win_sdk/Lib/10.0.14393.0/ucrt/$target_cpu",
+      "$windk/win_sdk/Lib/10.0.14393.0/um/$target_cpu",
     ]
     if (target_cpu == "x86") {
       lib_dirs += [ "$windk/VC/lib" ]
@@ -312,6 +315,7 @@
         "-Wno-shadow",
         "-Wno-shift-sign-overflow",
         "-Wno-sign-conversion",
+        "-Wno-signed-enum-bitfield",
         "-Wno-switch-enum",
         "-Wno-undef",
         "-Wno-unreachable-code",
diff --git a/gn/BUILDCONFIG.gn b/gn/BUILDCONFIG.gn
index a03ea82..23e5288 100644
--- a/gn/BUILDCONFIG.gn
+++ b/gn/BUILDCONFIG.gn
@@ -58,6 +58,8 @@
   ndk_platform = ""
   ndk_stdlib = ""
   ndk_gccdir = ""
+  ndk_gdbserver = ""
+  ndk_simpleperf = ""
 
   if (host_os == "linux") {
     ndk_host = "linux-x86_64"
@@ -72,31 +74,41 @@
     ndk_platform = "android-${ndk_api}/arch-arm64"
     ndk_stdlib = "arm64-v8a"
     ndk_gccdir = ndk_target
+    ndk_gdbserver = "prebuilt/android-arm64/gdbserver/gdbserver"
+    ndk_simpleperf = "simpleperf/android/arm64/simpleperf"
   } else if (target_cpu == "arm") {
     ndk_target = "arm-linux-androideabi"
     ndk_platform = "android-${ndk_api}/arch-arm"
     ndk_stdlib = "armeabi-v7a"
     ndk_gccdir = ndk_target
+    ndk_gdbserver = "prebuilt/android-arm/gdbserver/gdbserver"
+    ndk_simpleperf = "simpleperf/android/arm/simpleperf"
   } else if (target_cpu == "mips64el") {
     ndk_target = "mips64el-linux-android"
     ndk_platform = "android-${ndk_api}/arch-mips64"
     ndk_stdlib = "mips64"
     ndk_gccdir = ndk_target
+    ndk_gdbserver = "prebuilt/android-mips64/gdbserver/gdbserver"
   } else if (target_cpu == "mipsel") {
     ndk_target = "mipsel-linux-android"
     ndk_platform = "android-${ndk_api}/arch-mips"
     ndk_stdlib = "mips"
     ndk_gccdir = ndk_target
+    ndk_gdbserver = "prebuilt/android-mips/gdbserver/gdbserver"
   } else if (target_cpu == "x64") {
     ndk_target = "x86_64-linux-android"
     ndk_platform = "android-${ndk_api}/arch-x86_64"
     ndk_stdlib = "x86_64"
     ndk_gccdir = ndk_stdlib
+    ndk_gdbserver = "prebuilt/android-x86_64/gdbserver/gdbserver"
+    ndk_simpleperf = "simpleperf/android/x86_64/simpleperf"
   } else if (target_cpu == "x86") {
     ndk_target = "i686-linux-android"
     ndk_platform = "android-${ndk_api}/arch-x86"
     ndk_stdlib = "x86"
     ndk_gccdir = ndk_stdlib
+    ndk_gdbserver = "prebuilt/android-x86/gdbserver/gdbserver"
+    ndk_simpleperf = "simpleperf/android/x86/simpleperf"
   }
 }
 
diff --git a/gn/android_framework_defines.gni b/gn/android_framework_defines.gni
index f9b4c87..b693d28 100644
--- a/gn/android_framework_defines.gni
+++ b/gn/android_framework_defines.gni
@@ -13,7 +13,6 @@
   "SK_SUPPORT_LEGACY_DRAWFILTER",
   "SK_IGNORE_GPU_DITHER",
   "SK_SUPPORT_LEGACY_BITMAP_SETPIXELREF",
-  "SK_SUPPORT_LEGACY_IMAGE_ENCODER_CLASS",
   "SK_SUPPORT_LEGACY_SHADER_ISABITMAP",
   "SK_SUPPORT_LEGACY_EMBOSSMASKFILTER",
   "SK_SUPPORT_EXOTIC_CLIPOPS",
diff --git a/gn/core.gni b/gn/core.gni
index 2e45753..e5d72b3 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -50,6 +50,7 @@
   "$_src/core/SkBlitBWMaskTemplate.h",
   "$_src/core/SkBlitMask.h",
   "$_src/core/SkBlitMask_D32.cpp",
+  "$_src/core/SkBlitRow.h",
   "$_src/core/SkBlitRow_D16.cpp",
   "$_src/core/SkBlitRow_D32.cpp",
   "$_src/core/SkBlitter.h",
@@ -111,6 +112,7 @@
   "$_src/core/SkDither.h",
   "$_src/core/SkDocument.cpp",
   "$_src/core/SkDraw.cpp",
+  "$_src/core/SkDraw.h",
   "$_src/core/SkDrawable.cpp",
   "$_src/core/SkDrawLooper.cpp",
   "$_src/core/SkDrawProcs.h",
@@ -191,7 +193,6 @@
   "$_src/core/SkMatrixImageFilter.cpp",
   "$_src/core/SkMatrixImageFilter.h",
   "$_src/core/SkMatrixUtils.h",
-  "$_src/core/SkMessageBus.h",
   "$_src/core/SkMetaData.cpp",
   "$_src/core/SkMipMap.cpp",
   "$_src/core/SkMipMap.h",
@@ -381,7 +382,6 @@
   "$_include/core/SkBBHFactory.h",
   "$_include/core/SkBitmap.h",
   "$_include/core/SkBitmapDevice.h",
-  "$_include/core/SkBlitRow.h",
   "$_include/core/SkCanvas.h",
   "$_include/core/SkClipStack.h",
   "$_include/core/SkColor.h",
@@ -390,7 +390,6 @@
   "$_include/core/SkData.h",
   "$_include/core/SkDeque.h",
   "$_include/core/SkDevice.h",
-  "$_include/core/SkDraw.h",
   "$_include/core/SkDrawable.h",
   "$_include/core/SkDrawFilter.h",
   "$_include/core/SkDrawLooper.h",
@@ -437,7 +436,6 @@
   "$_include/core/SkStrokeRec.h",
   "$_include/core/SkSurface.h",
   "$_include/core/SkSwizzle.h",
-  "$_include/core/SkTRegistry.h",
   "$_include/core/SkTextBlob.h",
   "$_include/core/SkTime.h",
   "$_include/core/SkTLazy.h",
@@ -454,6 +452,7 @@
   "$_include/private/SkFloatBits.h",
   "$_include/private/SkFloatingPoint.h",
   "$_include/private/SkMiniRecorder.h",
+  "$_include/private/SkMessageBus.h",
   "$_include/private/SkMutex.h",
   "$_include/private/SkOnce.h",
   "$_include/private/SkRecords.h",
diff --git a/gn/effects.gni b/gn/effects.gni
index 10a2576..30e36b2 100644
--- a/gn/effects.gni
+++ b/gn/effects.gni
@@ -17,9 +17,8 @@
   "$_src/effects/Sk2DPathEffect.cpp",
   "$_src/effects/SkAlphaThresholdFilter.cpp",
   "$_src/effects/SkArcToPathEffect.cpp",
+  "$_src/effects/SkArithmeticImageFilter.cpp",
   "$_src/effects/SkArithmeticMode.cpp",
-  "$_src/effects/SkArithmeticMode_gpu.cpp",
-  "$_src/effects/SkArithmeticMode_gpu.h",
   "$_src/effects/SkBlurDrawLooper.cpp",
   "$_src/effects/SkBlurMask.cpp",
   "$_src/effects/SkBlurMask.h",
@@ -91,6 +90,7 @@
   "$_include/effects/Sk2DPathEffect.h",
   "$_include/effects/SkAlphaThresholdFilter.h",
   "$_include/effects/SkArithmeticMode.h",
+  "$_include/effects/SkArithmeticImageFilter.h",
   "$_include/effects/SkBlurDrawLooper.h",
   "$_include/effects/SkBlurImageFilter.h",
   "$_include/effects/SkBlurMaskFilter.h",
diff --git a/gn/gm.gni b/gn/gm.gni
index 8beb3e9..04ff445 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -113,6 +113,7 @@
   "$_gm/emptypath.cpp",
   "$_gm/encode.cpp",
   "$_gm/encode-platform.cpp",
+  "$_gm/encode-srgb.cpp",
   "$_gm/extractbitmap.cpp",
   "$_gm/fadefilter.cpp",
   "$_gm/fatpathfill.cpp",
@@ -228,6 +229,7 @@
   "$_gm/poly2poly.cpp",
   "$_gm/polygons.cpp",
   "$_gm/quadpaths.cpp",
+  "$_gm/readpixels.cpp",
   "$_gm/recordopts.cpp",
   "$_gm/rectangletexture.cpp",
   "$_gm/rects.cpp",
@@ -287,6 +289,7 @@
   "$_gm/textblobuseaftergpufree.cpp",
   "$_gm/texteffects.cpp",
   "$_gm/texturedomaineffect.cpp",
+  "$_gm/thinconcavepaths.cpp",
   "$_gm/thinrects.cpp",
   "$_gm/thinstrokedrects.cpp",
   "$_gm/tiledscaledbitmap.cpp",
diff --git a/gn/gpu.gni b/gn/gpu.gni
index a635401..eebbd74 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -90,6 +90,8 @@
   "$_src/gpu/GrCoordTransform.cpp",
   "$_src/gpu/GrDefaultGeoProcFactory.cpp",
   "$_src/gpu/GrDefaultGeoProcFactory.h",
+  "$_src/gpu/GrDistanceFieldGenFromVector.cpp",
+  "$_src/gpu/GrDistanceFieldGenFromVector.h",
   "$_src/gpu/GrDrawingManager.cpp",
   "$_src/gpu/GrDrawingManager.h",
   "$_src/gpu/GrDrawOpAtlas.cpp",
@@ -145,7 +147,6 @@
   "$_src/gpu/GrPrimitiveProcessor.h",
   "$_src/gpu/GrProgramDesc.cpp",
   "$_src/gpu/GrProgramDesc.h",
-  "$_src/gpu/GrProgramElement.cpp",
   "$_src/gpu/GrProcessor.cpp",
   "$_src/gpu/GrProcessorUnitTest.cpp",
   "$_src/gpu/GrProcOptInfo.cpp",
diff --git a/gn/push_to_android.py b/gn/push_to_android.py
new file mode 100755
index 0000000..b9ab36f
--- /dev/null
+++ b/gn/push_to_android.py
@@ -0,0 +1,21 @@
+#!/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.
+
+import os
+import subprocess
+import sys
+
+host, serial, stamp = sys.argv[1:]
+device = '/data/local/tmp/' + os.path.basename(host)
+
+# adb push is verbose, so eat its output with check_output().
+subprocess.check_output(['adb', '-s', serial, 'push', host, device])
+subprocess.check_call(['adb', '-s', serial, 'shell', 'chmod', '+x', device])
+
+# Touch a file to let GN/Ninja know we succeeded.
+with open(stamp, 'w'):
+  pass
diff --git a/gn/tests.gni b/gn/tests.gni
index 95e1a79..caafae4 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -98,6 +98,7 @@
   "$_tests/GrTextureStripAtlasTest.cpp",
   "$_tests/GrTRecorderTest.cpp",
   "$_tests/HashTest.cpp",
+  "$_tests/HSVRoundTripTest.cpp",
   "$_tests/image-bitmap.cpp",
   "$_tests/ICCTest.cpp",
   "$_tests/ImageCacheTest.cpp",
@@ -160,6 +161,7 @@
   "$_tests/PointTest.cpp",
   "$_tests/PremulAlphaRoundTripTest.cpp",
   "$_tests/PrimitiveProcessorTest.cpp",
+  "$_tests/ProcessorTest.cpp",
   "$_tests/ProxyConversionTest.cpp",
   "$_tests/ProxyRefTest.cpp",
   "$_tests/ProxyTest.cpp",
diff --git a/gyp/dm.gyp b/gyp/dm.gyp
index a8a7fd7..d5f71cf 100644
--- a/gyp/dm.gyp
+++ b/gyp/dm.gyp
@@ -16,10 +16,6 @@
           ['skia_android_framework', {
               'libraries': [
                   'skia_static.a',
-                  'hwui_static.a',
-              ],
-              'sources': [
-                '../dm/DMSrcSinkAndroid.cpp',
               ],
               'dependencies': [
                 'tools.gyp:android_utils',
diff --git a/gyp/pathops_skpclip.gyp b/gyp/pathops_skpclip.gyp
index 354caf3..95731e8 100755
--- a/gyp/pathops_skpclip.gyp
+++ b/gyp/pathops_skpclip.gyp
@@ -27,7 +27,6 @@
       ],
       'sources': [
         '../tests/PathOpsDebug.cpp',
-        '../tests/PathOpsSkpClipTest.cpp',
       ],
       'conditions': [
         [ 'skia_android_framework == 1', {
diff --git a/gyp/tests.gypi b/gyp/tests.gypi
index 517e256..9b97645 100644
--- a/gyp/tests.gypi
+++ b/gyp/tests.gypi
@@ -63,6 +63,5 @@
     '../tests/PathOpsCubicLineIntersectionIdeas.cpp',
     '../tests/PathOpsDebug.cpp',
     '../tests/PathOpsOpLoopThreadedTest.cpp',
-    '../tests/PathOpsSkpClipTest.cpp',
   ],
 }
diff --git a/include/c/sk_data.h b/include/c/sk_data.h
index 90333bb..863c619 100644
--- a/include/c/sk_data.h
+++ b/include/c/sk_data.h
@@ -19,7 +19,7 @@
     Returns a new empty sk_data_t.  This call must be balanced with a call to
     sk_data_unref().
 */
-SK_API sk_data_t* sk_data_new_empty();
+SK_API sk_data_t* sk_data_new_empty(void);
 /**
     Returns a new sk_data_t by copying the specified source data.
     This call must be balanced with a call to sk_data_unref().
diff --git a/include/c/sk_paint.h b/include/c/sk_paint.h
index e0886ad..ef7e624 100644
--- a/include/c/sk_paint.h
+++ b/include/c/sk_paint.h
@@ -28,7 +28,7 @@
         maskfilter : NULL
         xfermode_mode : SRCOVER_SK_XFERMODE_MODE
 */
-SK_API sk_paint_t* sk_paint_new();
+SK_API sk_paint_t* sk_paint_new(void);
 /**
     Release the memory storing the sk_paint_t and unref() all
     associated objects.
diff --git a/include/c/sk_path.h b/include/c/sk_path.h
index 6b4e83d..74abca0 100644
--- a/include/c/sk_path.h
+++ b/include/c/sk_path.h
@@ -21,7 +21,7 @@
 } sk_path_direction_t;
 
 /** Create a new, empty path. */
-SK_API sk_path_t* sk_path_new();
+SK_API sk_path_t* sk_path_new(void);
 /** Release the memory used by a sk_path_t. */
 SK_API void sk_path_delete(sk_path_t*);
 
diff --git a/include/c/sk_picture.h b/include/c/sk_picture.h
index 338b7d9..7a03214 100644
--- a/include/c/sk_picture.h
+++ b/include/c/sk_picture.h
@@ -19,7 +19,7 @@
     Create a new sk_picture_recorder_t.  Its resources should be
     released with a call to sk_picture_recorder_delete().
 */
-sk_picture_recorder_t* sk_picture_recorder_new();
+sk_picture_recorder_t* sk_picture_recorder_new(void);
 /**
     Release the memory and other resources used by this
     sk_picture_recorder_t.
diff --git a/include/c/sk_types.h b/include/c/sk_types.h
index baa3ac9..344259f 100644
--- a/include/c/sk_types.h
+++ b/include/c/sk_types.h
@@ -81,7 +81,7 @@
 /**
     Return the default sk_colortype_t; this is operating-system dependent.
 */
-SK_API sk_colortype_t sk_colortype_get_default_8888();
+SK_API sk_colortype_t sk_colortype_get_default_8888(void);
 
 typedef struct {
     int32_t         width;
diff --git a/include/config/SkUserConfig.h b/include/config/SkUserConfig.h
index 5ddc23b..c397f31 100644
--- a/include/config/SkUserConfig.h
+++ b/include/config/SkUserConfig.h
@@ -8,6 +8,7 @@
   #define SK_CODEC_DECODES_RAW
   #define SK_DEFAULT_FONT_CACHE_LIMIT   (768 * 1024)
   #define SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE (512 * 1024)
+  #define SK_ENABLE_DISCRETE_GPU
   #define SK_GAMMA_APPLY_TO_A8
   #define SK_GAMMA_CONTRAST 0.0
   #define SK_GAMMA_EXPONENT 1.4
@@ -25,9 +26,9 @@
   #define SK_SUPPORT_LEGACY_DRAWFILTER
   #define SK_SUPPORT_LEGACY_EMBOSSMASKFILTER
   #define SK_SUPPORT_LEGACY_GRADIENT_DITHERING
-  #define SK_SUPPORT_LEGACY_IMAGE_ENCODER_CLASS
   #define SK_SUPPORT_LEGACY_SHADER_ISABITMAP
   #define SK_SUPPORT_LEGACY_UNBALANCED_PIXELREF_LOCKCOUNT
+  #define SK_SUPPORT_PDF
   #define SK_USE_FREETYPE_EMBOLDEN
   #define SK_VULKAN
   #define SK_XML
diff --git a/include/core/SkBitmap.h b/include/core/SkBitmap.h
index 1f195c0..4d2af43 100644
--- a/include/core/SkBitmap.h
+++ b/include/core/SkBitmap.h
@@ -644,6 +644,21 @@
      */
     bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
                     int srcX, int srcY) const;
+    bool readPixels(const SkPixmap& dst, int srcX, int srcY) const;
+    bool readPixels(const SkPixmap& dst) const {
+        return this->readPixels(dst, 0, 0);
+    }
+
+    /**
+     *  Copy the src pixmap's pixels into this bitmap, offset by dstX, dstY.
+     *
+     *  This is logically the same as creating a bitmap around src, and calling readPixels on it
+     *  with this bitmap as the dst.
+     */
+    bool writePixels(const SkPixmap& src, int dstX, int dstY);
+    bool writePixels(const SkPixmap& src) {
+        return this->writePixels(src, 0, 0);
+    }
 
     /**
      *  Returns true if this bitmap's pixels can be converted into the requested
diff --git a/include/core/SkBitmapDevice.h b/include/core/SkBitmapDevice.h
index 7ad4abe..776b6a0 100644
--- a/include/core/SkBitmapDevice.h
+++ b/include/core/SkBitmapDevice.h
@@ -28,6 +28,7 @@
 class SkPath;
 class SkPixelRef;
 class SkPixmap;
+class SkRasterHandleAllocator;
 class SkRRect;
 class SkSurface;
 struct SkPoint;
@@ -54,12 +55,15 @@
      *  valid for the bitmap to have no pixels associated with it. In that case,
      *  any drawing to this device will have no effect.
      */
-    SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps);
+    SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps,
+                   void* externalHandle = nullptr);
 
-    static SkBitmapDevice* Create(const SkImageInfo&, const SkSurfaceProps&);
+    static SkBitmapDevice* Create(const SkImageInfo&, const SkSurfaceProps&,
+                                  SkRasterHandleAllocator* = nullptr);
 
 protected:
     bool onShouldDisableLCD(const SkPaint&) const override;
+    void* getRasterHandle() const override { return fRasterHandle; }
 
     /** These are called inside the per-device-layer loop for each draw call.
      When these are called, we have already applied any saveLayer operations,
@@ -172,6 +176,7 @@
     SkImageFilterCache* getImageFilterCache() override;
 
     SkBitmap    fBitmap;
+    void*       fRasterHandle = nullptr;
 
     void setNewSize(const SkISize&);  // Used by SkCanvas for resetForNextPicture().
 
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index 63f7f78..704584a 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -15,6 +15,7 @@
 #include "SkDeque.h"
 #include "SkImage.h"
 #include "SkPaint.h"
+#include "SkRasterHandleAllocator.h"
 #include "SkRefCnt.h"
 #include "SkRegion.h"
 #include "SkSurfaceProps.h"
@@ -217,6 +218,8 @@
      */
     void* accessTopLayerPixels(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin = NULL);
 
+    SkRasterHandleAllocator::Handle accessTopRasterHandle() const;
+
     /**
      *  If the canvas has readable pixels in its base layer (and is not recording to a picture
      *  or other non-raster target) and has direct access to its pixels (i.e. they are in
@@ -1569,6 +1572,7 @@
     int         fSaveCount;         // value returned by getSaveCount()
 
     SkMetaData* fMetaData;
+    std::unique_ptr<SkRasterHandleAllocator> fAllocator;
 
     SkSurface_Base*  fSurfaceBase;
     SkSurface_Base* getSurfaceBase() const { return fSurfaceBase; }
@@ -1599,6 +1603,7 @@
     friend class SkPicturePlayback; // SaveFlagsToSaveLayerFlags
     friend class SkDeferredCanvas;  // For use of resetForNextPicture
     friend class SkOverdrawCanvas;
+    friend class SkRasterHandleAllocator;
 
     enum InitFlags {
         kDefault_InitFlags                  = 0,
@@ -1606,6 +1611,8 @@
     };
     SkCanvas(const SkIRect& bounds, InitFlags);
     SkCanvas(SkBaseDevice* device, InitFlags);
+    SkCanvas(const SkBitmap&, std::unique_ptr<SkRasterHandleAllocator>,
+             SkRasterHandleAllocator::Handle);
 
     void resetForNextPicture(const SkIRect& bounds);
 
diff --git a/include/core/SkClipStack.h b/include/core/SkClipStack.h
index 55e0942..2f24d69 100644
--- a/include/core/SkClipStack.h
+++ b/include/core/SkClipStack.h
@@ -8,14 +8,19 @@
 #ifndef SkClipStack_DEFINED
 #define SkClipStack_DEFINED
 
+#include "../private/SkMessageBus.h"
 #include "SkCanvas.h"
 #include "SkDeque.h"
 #include "SkPath.h"
-#include "SkRect.h"
 #include "SkRRect.h"
+#include "SkRect.h"
 #include "SkRegion.h"
 #include "SkTLazy.h"
 
+#if SK_SUPPORT_GPU
+#include "GrResourceKey.h"
+#endif
+
 class SkCanvasClipVisitor;
 
 // Because a single save/restore state can have multiple clips, this class
@@ -72,6 +77,14 @@
             this->initPath(0, path, op, doAA);
         }
 
+        ~Element() {
+#if SK_SUPPORT_GPU
+            for (int i = 0; i < fMessages.count(); ++i) {
+                SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(*fMessages[i]);
+            }
+#endif
+        }
+
         bool operator== (const Element& element) const;
         bool operator!= (const Element& element) const { return !(*this == element); }
 
@@ -196,15 +209,26 @@
         void dump() const;
 #endif
 
+#if SK_SUPPORT_GPU
+        /**
+         * 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));
+        }
+#endif
+
     private:
         friend class SkClipStack;
 
         SkTLazy<SkPath> fPath;
-        SkRRect         fRRect;
-        int             fSaveCount; // save count of stack when this element was added.
-        SkClipOp        fOp;
-        Type            fType;
-        bool            fDoAA;
+        SkRRect fRRect;
+        int fSaveCount;  // save count of stack when this element was added.
+        SkClipOp fOp;
+        Type fType;
+        bool fDoAA;
 
         /* fFiniteBoundType and fFiniteBound are used to incrementally update the clip stack's
            bound. When fFiniteBoundType is kNormal_BoundsType, fFiniteBound represents the
@@ -217,14 +241,16 @@
            can capture the cancelling out of the extensions to infinity when two inverse filled
            clips are Booleaned together. */
         SkClipStack::BoundsType fFiniteBoundType;
-        SkRect                  fFiniteBound;
+        SkRect fFiniteBound;
 
         // When element is applied to the previous elements in the stack is the result known to be
         // equivalent to a single rect intersection? IIOW, is the clip effectively a rectangle.
-        bool                    fIsIntersectionOfRects;
+        bool fIsIntersectionOfRects;
 
-        int                     fGenID;
-
+        int fGenID;
+#if SK_SUPPORT_GPU
+        mutable SkTArray<std::unique_ptr<GrUniqueKeyInvalidatedMessage>> fMessages;
+#endif
         Element(int saveCount) {
             this->initCommon(saveCount, SkClipOp::kReplace_deprecated, false);
             this->setEmpty();
diff --git a/include/core/SkDevice.h b/include/core/SkDevice.h
index b0aac41..fa427c6 100644
--- a/include/core/SkDevice.h
+++ b/include/core/SkDevice.h
@@ -21,6 +21,7 @@
 struct SkIRect;
 class SkMatrix;
 class SkMetaData;
+class SkRasterHandleAllocator;
 class SkRegion;
 class SkSpecialImage;
 class GrRenderTarget;
@@ -112,6 +113,8 @@
      */
     const SkIPoint& getOrigin() const { return fOrigin; }
 
+    virtual void* getRasterHandle() const { return nullptr; }
+
 protected:
     enum TileUsage {
         kPossible_TileUsage,    //!< the created device may be drawn tiled
@@ -292,15 +295,18 @@
         CreateInfo(const SkImageInfo& info,
                    TileUsage tileUsage,
                    SkPixelGeometry geo,
-                   bool preserveLCDText)
+                   bool preserveLCDText,
+                   SkRasterHandleAllocator* allocator)
             : fInfo(info)
             , fTileUsage(tileUsage)
             , fPixelGeometry(AdjustGeometry(info, tileUsage, geo, preserveLCDText))
+            , fAllocator(allocator)
         {}
 
         const SkImageInfo       fInfo;
         const TileUsage         fTileUsage;
         const SkPixelGeometry   fPixelGeometry;
+        SkRasterHandleAllocator* fAllocator = nullptr;
     };
 
     /**
diff --git a/include/core/SkDocument.h b/include/core/SkDocument.h
index 418a837..64291fb 100644
--- a/include/core/SkDocument.h
+++ b/include/core/SkDocument.h
@@ -46,7 +46,7 @@
      */
     struct PDFMetadata {
         /**
-         * The document’s title.
+         * The document's title.
          */
         SkString fTitle;
         /**
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index 100298a..076da33 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -154,16 +154,27 @@
                                                    const SkISize nv12Sizes[2], GrSurfaceOrigin,
                                                    sk_sp<SkColorSpace> = nullptr);
 
-    /**
-     *  Create a new image from the specified picture. The color space becomes a preferred
-     *  color space for playback of the picture when retrieving the rasterized image contents.
-     */
-    static sk_sp<SkImage> MakeFromPicture(sk_sp<SkPicture>, const SkISize& dimensions,
-                                          const SkMatrix*, const SkPaint*, sk_sp<SkColorSpace>);
+    enum class BitDepth {
+        kU8,
+        kF16,
+    };
 
+    /**
+     *  Create a new image from the specified picture.
+     *  This SkImage has no defined BitDepth or SkColorSpace, it is a flexible container for
+     *  draw commands.
+     */
     static sk_sp<SkImage> MakeFromPicture(sk_sp<SkPicture> picture, const SkISize& dimensions,
                                           const SkMatrix* matrix, const SkPaint* paint);
 
+    /**
+     *  Create a new image from the specified picture.
+     *  On creation of the SkImage, snap the SkPicture to a particular BitDepth and SkColorSpace.
+     */
+    static sk_sp<SkImage> MakeFromPicture(sk_sp<SkPicture>, const SkISize& dimensions,
+                                          const SkMatrix*, const SkPaint*, BitDepth,
+                                          sk_sp<SkColorSpace>);
+
     static sk_sp<SkImage> MakeTextureFromPixmap(GrContext*, const SkPixmap&, SkBudgeted budgeted);
 
     ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -200,22 +211,6 @@
      */
     bool peekPixels(SkPixmap* pixmap) const;
 
-#ifdef SK_SUPPORT_LEGACY_PREROLL
-    /**
-     *  Some images have to perform preliminary work in preparation for drawing. This can be
-     *  decoding, uploading to a GPU, or other tasks. These happen automatically when an image
-     *  is drawn, and often they are cached so that the cost is only paid the first time.
-     *
-     *  Preroll() can be called before drawing to try to perform this prepatory work ahead of time.
-     *  For images that have no such work, this returns instantly. Others may do some thing to
-     *  prepare their cache and then return.
-     *
-     *  If the image will drawn to a GPU-backed canvas or surface, pass the associated GrContext.
-     *  If the image will be drawn to any other type of canvas or surface, pass null.
-     */
-    void preroll(GrContext* = nullptr) const;
-#endif
-
     // DEPRECATED - currently used by Canvas2DLayerBridge in Chromium.
     GrTexture* getTexture() const;
 
@@ -285,11 +280,6 @@
      *  even if the image returns a data from refEncoded(). That data will be ignored.
      */
     SkData* encode(SkEncodedImageFormat, int quality) const;
-#ifdef SK_SUPPORT_LEGACY_IMAGE_ENCODER_CLASS
-    SkData* encode(SkImageEncoder::Type t, int quality) const {
-        return this->encode((SkEncodedImageFormat)t, quality);
-    }
-#endif
 
     /**
      *  Encode the image and return the result as a caller-managed SkData.  This will
diff --git a/include/core/SkImageEncoder.h b/include/core/SkImageEncoder.h
index 17d0603..e4f746a 100644
--- a/include/core/SkImageEncoder.h
+++ b/include/core/SkImageEncoder.h
@@ -39,36 +39,4 @@
     return src.peekPixels(&pixmap) && SkEncodeImage(dst, pixmap, f, q);
 }
 
-//TODO(halcanary):  remove this code once all changes land.
-#ifdef SK_SUPPORT_LEGACY_IMAGE_ENCODER_CLASS
-class SkImageEncoder {
-public:
-    enum Type {
-#ifdef GOOGLE3
-        kUnknown_Type = (int)SkEncodedImageFormat::kUnknown,
-#endif
-        kBMP_Type     = (int)SkEncodedImageFormat::kBMP,
-        kGIF_Type     = (int)SkEncodedImageFormat::kGIF,
-        kICO_Type     = (int)SkEncodedImageFormat::kICO,
-        kJPEG_Type    = (int)SkEncodedImageFormat::kJPEG,
-        kPNG_Type     = (int)SkEncodedImageFormat::kPNG,
-        kWBMP_Type    = (int)SkEncodedImageFormat::kWBMP,
-        kWEBP_Type    = (int)SkEncodedImageFormat::kWEBP,
-        kKTX_Type     = (int)SkEncodedImageFormat::kKTX,
-    };
-    static SkData* EncodeData(const SkBitmap& src, Type t, int quality) {
-        SkDynamicMemoryWStream buf;
-        return SkEncodeImage(&buf, src, (SkEncodedImageFormat)t, quality)
-            ? buf.detachAsData().release() : nullptr;
-    }
-    static bool EncodeFile(const char path[], const SkBitmap& src, Type t, int quality) {
-        SkFILEWStream file(path);
-        return SkEncodeImage(&file, src, (SkEncodedImageFormat)t, quality);
-    }
-    static bool EncodeStream(SkWStream* dst, const SkBitmap& bm, Type t, int quality) {
-        return SkEncodeImage(dst, bm, (SkEncodedImageFormat)t, quality);
-    }
-};
-#endif  // SK_SUPPORT_LEGACY_IMAGE_ENCODER_CLASS
-
 #endif  // SkImageEncoder_DEFINED
diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h
index c2d7261..86e12e1 100644
--- a/include/core/SkImageFilter.h
+++ b/include/core/SkImageFilter.h
@@ -386,6 +386,15 @@
      */
     Context mapContext(const Context& ctx) const;
 
+#if SK_SUPPORT_GPU
+    /**
+     *  Returns a version of the passed-in image (possibly the original), that is in a colorspace
+     *  with the same gamut as the one from the OutputProperties. This allows filters that do many
+     *  texture samples to guarantee that any color space conversion has happened before running.
+     */
+    static sk_sp<SkSpecialImage> ImageToColorSpace(SkSpecialImage* src, const OutputProperties&);
+#endif
+
 private:
     friend class SkGraphics;
     static void PurgeCache();
diff --git a/include/core/SkImageGenerator.h b/include/core/SkImageGenerator.h
index 0ab27ae..aec940f 100644
--- a/include/core/SkImageGenerator.h
+++ b/include/core/SkImageGenerator.h
@@ -10,6 +10,7 @@
 
 #include "SkBitmap.h"
 #include "SkColor.h"
+#include "SkImage.h"
 #include "SkImageInfo.h"
 #include "SkYUVSizeInfo.h"
 
@@ -19,8 +20,6 @@
 class GrSamplerParams;
 class SkBitmap;
 class SkData;
-class SkImage;
-class SkImageGenerator;
 class SkMatrix;
 class SkPaint;
 class SkPicture;
@@ -228,7 +227,7 @@
      *  time.
      */
     static SkImageGenerator* NewFromPicture(const SkISize&, const SkPicture*, const SkMatrix*,
-                                            const SkPaint*, sk_sp<SkColorSpace>);
+                                            const SkPaint*, SkImage::BitDepth, sk_sp<SkColorSpace>);
 
     bool tryGenerateBitmap(SkBitmap* bm, const SkImageInfo& info, SkBitmap::Allocator* allocator);
 
diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h
index 198cf37..3475429 100644
--- a/include/core/SkMaskFilter.h
+++ b/include/core/SkMaskFilter.h
@@ -112,23 +112,23 @@
                                   SkRect* maskRect) const;
 
     /**
-     *  Try to directly render the mask filter into the target.  Returns
-     *  true if drawing was successful.
+     *  Try to directly render the mask filter into the target. Returns true if drawing was
+     *  successful. If false is returned then paint is unmodified.
      */
     virtual bool directFilterMaskGPU(GrTextureProvider* texProvider,
                                      GrRenderTargetContext* renderTargetContext,
-                                     GrPaint* grp,
+                                     GrPaint&& paint,
                                      const GrClip&,
                                      const SkMatrix& viewMatrix,
                                      const SkStrokeRec& strokeRec,
                                      const SkPath& path) const;
     /**
      *  Try to directly render a rounded rect mask filter into the target.  Returns
-     *  true if drawing was successful.
+     *  true if drawing was successful.  If false is returned then paint is unmodified.
      */
     virtual bool directFilterRRectMaskGPU(GrContext*,
                                           GrRenderTargetContext* renderTargetContext,
-                                          GrPaint* grp,
+                                          GrPaint&& paint,
                                           const GrClip&,
                                           const SkMatrix& viewMatrix,
                                           const SkStrokeRec& strokeRec,
diff --git a/include/core/SkMatrix44.h b/include/core/SkMatrix44.h
index 9820ee5..83e51ab 100644
--- a/include/core/SkMatrix44.h
+++ b/include/core/SkMatrix44.h
@@ -491,7 +491,10 @@
         return 0 == fTypeMask;
     }
 
+    inline const SkMScalar* values() const { return &fMat[0][0]; }
+
     friend class SkColorSpace;
+    friend class SkColorSpace_XYZ;
 };
 
 #endif
diff --git a/include/core/SkRasterHandleAllocator.h b/include/core/SkRasterHandleAllocator.h
new file mode 100644
index 0000000..62796cb
--- /dev/null
+++ b/include/core/SkRasterHandleAllocator.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkRasterHandleAllocator_DEFINED
+#define SkRasterHandleAllocator_DEFINED
+
+#include "SkImageInfo.h"
+
+class SkCanvas;
+class SkMatrix;
+
+/**
+ *  If a client wants to control the allocation of raster layers in a canvas, it should subclass
+ *  SkRasterHandleAllocator. This allocator performs two tasks:
+ *      1. controls how the memory for the pixels is allocated
+ *      2. associates a "handle" to a private object that can track the matrix/clip of the SkCanvas
+ *
+ *  This example allocates a canvas, and defers to the allocator to create the base layer.
+ *
+ *      std::unique_ptr<SkCanvas> canvas = SkRasterHandleAllocator::MakeCanvas(
+ *              SkImageInfo::Make(...),
+ *              skstd::make_unique<MySubclassRasterHandleAllocator>(...),
+ *              nullptr);
+ *
+ *  If you have already allocated the base layer (and its handle, release-proc etc.) then you
+ *  can pass those in using the last parameter to MakeCanvas().
+ *
+ *  Regardless of how the base layer is allocated, each time canvas->saveLayer() is called,
+ *  your allocator's allocHandle() will be called.
+ */
+class SK_API SkRasterHandleAllocator {
+public:
+    virtual ~SkRasterHandleAllocator() {}
+
+    // The value that is returned to clients of the canvas that has this allocator installed.
+    typedef void* Handle;
+
+    struct Rec {
+        // When the allocation goes out of scope, this proc is called to free everything associated
+        // with it: the pixels, the "handle", etc. This is passed the pixel address and fReleaseCtx.
+        void    (*fReleaseProc)(void* pixels, void* ctx);
+        void*   fReleaseCtx;    // context passed to fReleaseProc
+        void*   fPixels;        // pixels for this allocation
+        size_t  fRowBytes;      // rowbytes for these pixels
+        Handle  fHandle;        // public handle returned by SkCanvas::accessTopRasterHandle()
+    };
+
+    /**
+     *  Given a requested info, allocate the corresponding pixels/rowbytes, and whatever handle
+     *  is desired to give clients access to those pixels. The rec also contains a proc and context
+     *  which will be called when this allocation goes out of scope.
+     *
+     *  e.g.
+     *      when canvas->saveLayer() is called, the allocator will be called to allocate the pixels
+     *      for the layer. When canvas->restore() is called, the fReleaseProc will be called.
+     */
+    virtual bool allocHandle(const SkImageInfo&, Rec*) = 0;
+
+    /**
+     *  Clients access the handle for a given layer by calling SkCanvas::accessTopRasterHandle().
+     *  To allow the handle to reflect the current matrix/clip in the canvs, updateHandle() is
+     *  is called. The subclass is responsible to update the handle as it sees fit.
+     */
+    virtual void updateHandle(Handle, const SkMatrix&, const SkIRect&) = 0;
+
+    /**
+     *  This creates a canvas which will use the allocator to manage pixel allocations, including
+     *  all calls to saveLayer().
+     *
+     *  If rec is non-null, then it will be used as the base-layer of pixels/handle.
+     *  If rec is null, then the allocator will be called for the base-layer as well.
+     */
+    static std::unique_ptr<SkCanvas> MakeCanvas(std::unique_ptr<SkRasterHandleAllocator>,
+                                                const SkImageInfo&, const Rec* rec = nullptr);
+
+private:
+    friend class SkBitmapDevice;
+
+    Handle allocBitmap(const SkImageInfo&, SkBitmap*);
+};
+
+#endif
diff --git a/include/core/SkShader.h b/include/core/SkShader.h
index 6d24c1a..0519fe1 100644
--- a/include/core/SkShader.h
+++ b/include/core/SkShader.h
@@ -17,6 +17,10 @@
 #include "SkPaint.h"
 #include "../gpu/GrColor.h"
 
+#ifdef GOOGLE3
+#include "../private/SkAutoMalloc.h"
+#endif
+
 class SkColorFilter;
 class SkColorSpace;
 class SkFallbackAlloc;
diff --git a/include/core/SkStream.h b/include/core/SkStream.h
index 83eee47..419ef7b 100644
--- a/include/core/SkStream.h
+++ b/include/core/SkStream.h
@@ -187,7 +187,6 @@
         @return true on success
     */
     virtual bool write(const void* buffer, size_t size) = 0;
-    virtual void newline();
     virtual void flush();
 
     virtual size_t bytesWritten() const = 0;
@@ -210,6 +209,9 @@
         SkASSERT(text);
         return this->write(text, strlen(text));
     }
+
+    bool newline() { return this->write("\n", strlen("\n")); }
+
     bool    writeDecAsText(int32_t);
     bool    writeBigDecAsText(int64_t, int minDigits = 0);
     bool    writeHexAsText(uint32_t, int minDigits = 0);
@@ -364,20 +366,6 @@
     typedef SkWStream INHERITED;
 };
 
-class SK_API SkMemoryWStream : public SkWStream {
-public:
-    SkMemoryWStream(void* buffer, size_t size);
-    bool write(const void* buffer, size_t size) override;
-    size_t bytesWritten() const override { return fBytesWritten; }
-
-private:
-    char*   fBuffer;
-    size_t  fMaxLength;
-    size_t  fBytesWritten;
-
-    typedef SkWStream INHERITED;
-};
-
 class SK_API SkDynamicMemoryWStream : public SkWStream {
 public:
     SkDynamicMemoryWStream();
@@ -420,22 +408,4 @@
     typedef SkWStream INHERITED;
 };
 
-
-class SK_API SkDebugWStream : public SkWStream {
-public:
-    SkDebugWStream() : fBytesWritten(0) {}
-
-    // overrides
-    bool write(const void* buffer, size_t size) override;
-    void newline() override;
-    size_t bytesWritten() const override { return fBytesWritten; }
-
-private:
-    size_t fBytesWritten;
-    typedef SkWStream INHERITED;
-};
-
-// for now
-typedef SkFILEStream SkURLStream;
-
 #endif
diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h
index eb8f6ae..be657b1 100644
--- a/include/core/SkSurface.h
+++ b/include/core/SkSurface.h
@@ -257,18 +257,6 @@
     sk_sp<SkImage> makeImageSnapshot(SkBudgeted = SkBudgeted::kYes);
 
     /**
-     * In rare instances a client may want a unique copy of the SkSurface's contents in an image
-     * snapshot. This enum can be used to enforce that the image snapshot's backing store is not
-     * shared with another image snapshot or the surface's backing store. This is generally more
-     * expensive. This was added for Chromium bug 585250.
-     */
-    enum ForceUnique {
-        kNo_ForceUnique,
-        kYes_ForceUnique
-    };
-    sk_sp<SkImage> makeImageSnapshot(SkBudgeted, ForceUnique);
-
-    /**
      *  Though the caller could get a snapshot image explicitly, and draw that,
      *  it seems that directly drawing a surface into another canvas might be
      *  a common pattern, and that we could possibly be more efficient, since
diff --git a/include/core/SkTypes.h b/include/core/SkTypes.h
index 2cc4b9c..f9e1a05 100644
--- a/include/core/SkTypes.h
+++ b/include/core/SkTypes.h
@@ -509,222 +509,6 @@
     SkNoncopyable& operator=(const SkNoncopyable&);
 };
 
-class SkAutoFree : SkNoncopyable {
-public:
-    SkAutoFree() : fPtr(NULL) {}
-    explicit SkAutoFree(void* ptr) : fPtr(ptr) {}
-    ~SkAutoFree() { sk_free(fPtr); }
-
-    /** Return the currently allocate buffer, or null
-    */
-    void* get() const { return fPtr; }
-
-    /** Assign a new ptr allocated with sk_malloc (or null), and return the
-        previous ptr. Note it is the caller's responsibility to sk_free the
-        returned ptr.
-    */
-    void* set(void* ptr) {
-        void* prev = fPtr;
-        fPtr = ptr;
-        return prev;
-    }
-
-    /** Transfer ownership of the current ptr to the caller, setting the
-        internal reference to null. Note the caller is reponsible for calling
-        sk_free on the returned address.
-    */
-    void* release() { return this->set(NULL); }
-
-    /** Free the current buffer, and set the internal reference to NULL. Same
-        as calling sk_free(release())
-    */
-    void reset() {
-        sk_free(fPtr);
-        fPtr = NULL;
-    }
-
-private:
-    void* fPtr;
-    // illegal
-    SkAutoFree(const SkAutoFree&);
-    SkAutoFree& operator=(const SkAutoFree&);
-};
-#define SkAutoFree(...) SK_REQUIRE_LOCAL_VAR(SkAutoFree)
-
-/**
- *  Manage an allocated block of heap memory. This object is the sole manager of
- *  the lifetime of the block, so the caller must not call sk_free() or delete
- *  on the block, unless release() was called.
- */
-class SkAutoMalloc : SkNoncopyable {
-public:
-    explicit SkAutoMalloc(size_t size = 0) {
-        fPtr = size ? sk_malloc_throw(size) : NULL;
-        fSize = size;
-    }
-
-    ~SkAutoMalloc() {
-        sk_free(fPtr);
-    }
-
-    /**
-     *  Passed to reset to specify what happens if the requested size is smaller
-     *  than the current size (and the current block was dynamically allocated).
-     */
-    enum OnShrink {
-        /**
-         *  If the requested size is smaller than the current size, and the
-         *  current block is dynamically allocated, free the old block and
-         *  malloc a new block of the smaller size.
-         */
-        kAlloc_OnShrink,
-
-        /**
-         *  If the requested size is smaller than the current size, and the
-         *  current block is dynamically allocated, just return the old
-         *  block.
-         */
-        kReuse_OnShrink
-    };
-
-    /**
-     *  Reallocates the block to a new size. The ptr may or may not change.
-     */
-    void* reset(size_t size = 0, OnShrink shrink = kAlloc_OnShrink,  bool* didChangeAlloc = NULL) {
-        if (size == fSize || (kReuse_OnShrink == shrink && size < fSize)) {
-            if (didChangeAlloc) {
-                *didChangeAlloc = false;
-            }
-            return fPtr;
-        }
-
-        sk_free(fPtr);
-        fPtr = size ? sk_malloc_throw(size) : NULL;
-        fSize = size;
-        if (didChangeAlloc) {
-            *didChangeAlloc = true;
-        }
-
-        return fPtr;
-    }
-
-    /**
-     *  Return the allocated block.
-     */
-    void* get() { return fPtr; }
-    const void* get() const { return fPtr; }
-
-   /** Transfer ownership of the current ptr to the caller, setting the
-       internal reference to null. Note the caller is reponsible for calling
-       sk_free on the returned address.
-    */
-    void* release() {
-        void* ptr = fPtr;
-        fPtr = NULL;
-        fSize = 0;
-        return ptr;
-    }
-
-private:
-    void*   fPtr;
-    size_t  fSize;  // can be larger than the requested size (see kReuse)
-};
-#define SkAutoMalloc(...) SK_REQUIRE_LOCAL_VAR(SkAutoMalloc)
-
-/**
- *  Manage an allocated block of memory. If the requested size is <= kSizeRequested (or slightly
- *  more), then the allocation will come from the stack rather than the heap. This object is the
- *  sole manager of the lifetime of the block, so the caller must not call sk_free() or delete on
- *  the block.
- */
-template <size_t kSizeRequested> class SkAutoSMalloc : SkNoncopyable {
-public:
-    /**
-     *  Creates initially empty storage. get() returns a ptr, but it is to a zero-byte allocation.
-     *  Must call reset(size) to return an allocated block.
-     */
-    SkAutoSMalloc() {
-        fPtr = fStorage;
-        fSize = kSize;
-    }
-
-    /**
-     *  Allocate a block of the specified size. If size <= kSizeRequested (or slightly more), then
-     *  the allocation will come from the stack, otherwise it will be dynamically allocated.
-     */
-    explicit SkAutoSMalloc(size_t size) {
-        fPtr = fStorage;
-        fSize = kSize;
-        this->reset(size);
-    }
-
-    /**
-     *  Free the allocated block (if any). If the block was small enough to have been allocated on
-     *  the stack, then this does nothing.
-     */
-    ~SkAutoSMalloc() {
-        if (fPtr != (void*)fStorage) {
-            sk_free(fPtr);
-        }
-    }
-
-    /**
-     *  Return the allocated block. May return non-null even if the block is of zero size. Since
-     *  this may be on the stack or dynamically allocated, the caller must not call sk_free() on it,
-     *  but must rely on SkAutoSMalloc to manage it.
-     */
-    void* get() const { return fPtr; }
-
-    /**
-     *  Return a new block of the requested size, freeing (as necessary) any previously allocated
-     *  block. As with the constructor, if size <= kSizeRequested (or slightly more) then the return
-     *  block may be allocated locally, rather than from the heap.
-     */
-    void* reset(size_t size,
-                SkAutoMalloc::OnShrink shrink = SkAutoMalloc::kAlloc_OnShrink,
-                bool* didChangeAlloc = NULL) {
-        size = (size < kSize) ? kSize : size;
-        bool alloc = size != fSize && (SkAutoMalloc::kAlloc_OnShrink == shrink || size > fSize);
-        if (didChangeAlloc) {
-            *didChangeAlloc = alloc;
-        }
-        if (alloc) {
-            if (fPtr != (void*)fStorage) {
-                sk_free(fPtr);
-            }
-
-            if (size == kSize) {
-                SkASSERT(fPtr != fStorage); // otherwise we lied when setting didChangeAlloc.
-                fPtr = fStorage;
-            } else {
-                fPtr = sk_malloc_flags(size, SK_MALLOC_THROW | SK_MALLOC_TEMP);
-            }
-
-            fSize = size;
-        }
-        SkASSERT(fSize >= size && fSize >= kSize);
-        SkASSERT((fPtr == fStorage) || fSize > kSize);
-        return fPtr;
-    }
-
-private:
-    // Align up to 32 bits.
-    static const size_t kSizeAlign4 = SkAlign4(kSizeRequested);
-#if defined(GOOGLE3)
-    // Stack frame size is limited for GOOGLE3. 4k is less than the actual max, but some functions
-    // have multiple large stack allocations.
-    static const size_t kMaxBytes = 4 * 1024;
-    static const size_t kSize = kSizeRequested > kMaxBytes ? kMaxBytes : kSizeAlign4;
-#else
-    static const size_t kSize = kSizeAlign4;
-#endif
-
-    void*       fPtr;
-    size_t      fSize;  // can be larger than the requested size (see kReuse)
-    uint32_t    fStorage[kSize >> 2];
-};
-// Can't guard the constructor because it's a template class.
-
 #endif /* C++ */
 
 #endif
diff --git a/include/effects/SkArithmeticImageFilter.h b/include/effects/SkArithmeticImageFilter.h
new file mode 100644
index 0000000..ef59603
--- /dev/null
+++ b/include/effects/SkArithmeticImageFilter.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkArithmeticImageFilter_DEFINED
+#define SkArithmeticImageFilter_DEFINED
+
+#include "SkImageFilter.h"
+
+class SK_API SkArithmeticImageFilter {
+public:
+    static sk_sp<SkImageFilter> Make(float k1, float k2, float k3, float k4, bool enforcePMColor,
+                                     sk_sp<SkImageFilter> background,
+                                     sk_sp<SkImageFilter> foreground,
+                                     const SkImageFilter::CropRect* cropRect);
+    static sk_sp<SkImageFilter> Make(float k1, float k2, float k3, float k4, bool enforcePMColor,
+                                     sk_sp<SkImageFilter> background) {
+        return Make(k1, k2, k3, k4, enforcePMColor, std::move(background), nullptr, nullptr);
+    }
+
+    SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP();
+
+private:
+    SkArithmeticImageFilter();  // can't instantiate
+};
+
+#endif
diff --git a/include/effects/SkXfermodeImageFilter.h b/include/effects/SkXfermodeImageFilter.h
index 5fbd1af..5e8b587 100644
--- a/include/effects/SkXfermodeImageFilter.h
+++ b/include/effects/SkXfermodeImageFilter.h
@@ -8,13 +8,13 @@
 #ifndef SkXfermodeImageFilter_DEFINED
 #define SkXfermodeImageFilter_DEFINED
 
+#include "SkArithmeticImageFilter.h"
 #include "SkBlendMode.h"
 #include "SkImageFilter.h"
 
 /**
- * This filter takes an xfermode, and uses it to composite the foreground
- * over the background.  If foreground or background is NULL, the input
- * bitmap (src) is used instead.
+ * This filter takes a SkBlendMode, and uses it to composite the foreground over the background.
+ * If foreground or background is NULL, the input bitmap (src) is used instead.
  */
 class SK_API SkXfermodeImageFilter {
 public:
@@ -25,16 +25,19 @@
         return Make(mode, std::move(background), nullptr, nullptr);
     }
 
+    // Arithmetic image filtering used to be implemented using SkXfermode. Some clients still rely
+    // on these factories existing in this class.
     static sk_sp<SkImageFilter> MakeArithmetic(float k1, float k2, float k3, float k4,
-                                               bool enforcePMColor,
-                                               sk_sp<SkImageFilter> background,
+                                               bool enforcePMColor, sk_sp<SkImageFilter> background,
                                                sk_sp<SkImageFilter> foreground,
-                                               const SkImageFilter::CropRect* cropRect);
+                                               const SkImageFilter::CropRect* cropRect) {
+        return SkArithmeticImageFilter::Make(k1, k2, k3, k4, enforcePMColor, std::move(background),
+                                             std::move(foreground), cropRect);
+    }
     static sk_sp<SkImageFilter> MakeArithmetic(float k1, float k2, float k3, float k4,
                                                bool enforcePMColor,
                                                sk_sp<SkImageFilter> background) {
-        return MakeArithmetic(k1, k2, k3, k4, enforcePMColor, std::move(background),
-                              nullptr, nullptr);
+        return SkArithmeticImageFilter::Make(k1, k2, k3, k4, enforcePMColor, std::move(background));
     }
 
     SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP();
diff --git a/include/gpu/GrColor.h b/include/gpu/GrColor.h
index 7277f0b..0403e48 100644
--- a/include/gpu/GrColor.h
+++ b/include/gpu/GrColor.h
@@ -288,7 +288,7 @@
     static const uint32_t kFlags[] = {
         0,                              // kUnknown_GrPixelConfig
         kA_GrColorComponentFlag,        // kAlpha_8_GrPixelConfig
-        kRGBA_GrColorComponentFlags,    // kIndex_8_GrPixelConfig
+        kRGB_GrColorComponentFlags,     // kGray_8_GrPixelConfig
         kRGB_GrColorComponentFlags,     // kRGB_565_GrPixelConfig
         kRGBA_GrColorComponentFlags,    // kRGBA_4444_GrPixelConfig
         kRGBA_GrColorComponentFlags,    // kRGBA_8888_GrPixelConfig
@@ -308,7 +308,7 @@
 
     GR_STATIC_ASSERT(0  == kUnknown_GrPixelConfig);
     GR_STATIC_ASSERT(1  == kAlpha_8_GrPixelConfig);
-    GR_STATIC_ASSERT(2  == kIndex_8_GrPixelConfig);
+    GR_STATIC_ASSERT(2  == kGray_8_GrPixelConfig);
     GR_STATIC_ASSERT(3  == kRGB_565_GrPixelConfig);
     GR_STATIC_ASSERT(4  == kRGBA_4444_GrPixelConfig);
     GR_STATIC_ASSERT(5  == kRGBA_8888_GrPixelConfig);
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 8d3b4f5..6664db9 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -260,11 +260,13 @@
     /**
      * Reads a rectangle of pixels from a surface.
      * @param surface       the surface to read from.
+     * @param srcColorSpace color space of the surface
      * @param left          left edge of the rectangle to read (inclusive)
      * @param top           top edge of the rectangle to read (inclusive)
      * @param width         width of rectangle to read in pixels.
      * @param height        height of rectangle to read in pixels.
      * @param config        the pixel config of the destination buffer
+     * @param dstColorSpace color space of the destination buffer
      * @param buffer        memory to read the rectangle into.
      * @param rowBytes      number of bytes bewtween consecutive rows. Zero means rows are tightly
      *                      packed.
@@ -273,20 +275,22 @@
      * @return true if the read succeeded, false if not. The read can fail because of an unsupported
      *         pixel configs
      */
-    bool readSurfacePixels(GrSurface* surface,
+    bool readSurfacePixels(GrSurface* surface, SkColorSpace* srcColorSpace,
                            int left, int top, int width, int height,
-                           GrPixelConfig config, void* buffer,
+                           GrPixelConfig config, SkColorSpace* dstColorSpace, void* buffer,
                            size_t rowBytes = 0,
                            uint32_t pixelOpsFlags = 0);
 
     /**
      * Writes a rectangle of pixels to a surface.
      * @param surface       the surface to write to.
+     * @param dstColorSpace color space of the surface
      * @param left          left edge of the rectangle to write (inclusive)
      * @param top           top edge of the rectangle to write (inclusive)
      * @param width         width of rectangle to write in pixels.
      * @param height        height of rectangle to write in pixels.
      * @param config        the pixel config of the source buffer
+     * @param srcColorSpace color space of the source buffer
      * @param buffer        memory to read pixels from
      * @param rowBytes      number of bytes between consecutive rows. Zero
      *                      means rows are tightly packed.
@@ -294,9 +298,9 @@
      * @return true if the write succeeded, false if not. The write can fail because of an
      *         unsupported combination of surface and src configs.
      */
-    bool writeSurfacePixels(GrSurface* surface,
+    bool writeSurfacePixels(GrSurface* surface, SkColorSpace* dstColorSpace,
                             int left, int top, int width, int height,
-                            GrPixelConfig config, const void* buffer,
+                            GrPixelConfig config, SkColorSpace* srcColorSpace, const void* buffer,
                             size_t rowBytes,
                             uint32_t pixelOpsFlags = 0);
 
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index 29d33df..cc7e7aa 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -94,6 +94,9 @@
 
 private:
     friend class GrIORefProxy; // needs to forward on wrapped IO calls
+    // This is for a unit test.
+    template <typename T>
+    friend void testingOnly_getIORefCnts(const T*, int* refCnt, int* readCnt, int* writeCnt);
 
     void addPendingRead() const {
         this->validate();
diff --git a/include/gpu/GrGpuResourceRef.h b/include/gpu/GrGpuResourceRef.h
index a51d814..d6da1d2 100644
--- a/include/gpu/GrGpuResourceRef.h
+++ b/include/gpu/GrGpuResourceRef.h
@@ -79,7 +79,7 @@
         called. */
     void pendingIOComplete() const;
 
-    friend class GrProgramElement;
+    friend class GrProcessor;
 
     GrGpuResource*  fResource;
     mutable bool    fOwnRef;
diff --git a/include/gpu/GrPaint.h b/include/gpu/GrPaint.h
index 6cdc2de..0f3b525 100644
--- a/include/gpu/GrPaint.h
+++ b/include/gpu/GrPaint.h
@@ -12,13 +12,13 @@
 
 #include "GrColor.h"
 #include "GrColorSpaceXform.h"
-#include "GrXferProcessor.h"
-#include "effects/GrPorterDuffXferProcessor.h"
 #include "GrFragmentProcessor.h"
-
+#include "GrXferProcessor.h"
 #include "SkBlendMode.h"
 #include "SkRefCnt.h"
 #include "SkRegion.h"
+#include "SkTLazy.h"
+#include "effects/GrPorterDuffXferProcessor.h"
 
 /**
  * The paint describes how color and coverage are computed at each pixel by GrContext draw
@@ -39,11 +39,9 @@
  */
 class GrPaint {
 public:
-    GrPaint();
-
-    GrPaint(const GrPaint& paint) { *this = paint; }
-
-    ~GrPaint() { }
+    GrPaint() = default;
+    explicit GrPaint(const GrPaint&) = default;
+    ~GrPaint() = default;
 
     /**
      * The initial color of the drawn primitive. Defaults to solid white.
@@ -84,13 +82,9 @@
         setAllowSRGBInputs(gammaCorrect);
     }
 
-    void setXPFactory(sk_sp<GrXPFactory> xpFactory) {
-        fXPFactory = std::move(xpFactory);
-    }
+    void setXPFactory(const GrXPFactory* xpFactory) { fXPFactory = xpFactory; }
 
-    void setPorterDuffXPFactory(SkBlendMode mode) {
-        fXPFactory = GrPorterDuffXPFactory::Make(mode);
-    }
+    void setPorterDuffXPFactory(SkBlendMode mode) { fXPFactory = GrPorterDuffXPFactory::Get(mode); }
 
     void setCoverageSetOpXPFactory(SkRegion::Op, bool invertCoverage = false);
 
@@ -127,9 +121,7 @@
     int numTotalFragmentProcessors() const { return this->numColorFragmentProcessors() +
                                               this->numCoverageFragmentProcessors(); }
 
-    GrXPFactory* getXPFactory() const {
-        return fXPFactory.get();
-    }
+    const GrXPFactory* getXPFactory() const { return fXPFactory; }
 
     GrFragmentProcessor* getColorFragmentProcessor(int i) const {
         return fColorFragmentProcessors[i].get();
@@ -138,20 +130,6 @@
         return fCoverageFragmentProcessors[i].get();
     }
 
-    GrPaint& operator=(const GrPaint& paint) {
-        fDisableOutputConversionToSRGB = paint.fDisableOutputConversionToSRGB;
-        fAllowSRGBInputs = paint.fAllowSRGBInputs;
-        fUsesDistanceVectorField = paint.fUsesDistanceVectorField;
-
-        fColor = paint.fColor;
-        fColorFragmentProcessors = paint.fColorFragmentProcessors;
-        fCoverageFragmentProcessors = paint.fCoverageFragmentProcessors;
-
-        fXPFactory = paint.fXPFactory;
-
-        return *this;
-    }
-
     /**
      * Returns true if the paint's output color will be constant after blending. If the result is
      * true, constantColor will be updated to contain the constant color. Note that we can conflate
@@ -171,17 +149,61 @@
     }
 
 private:
+    template <bool> class MoveOrImpl;
+
+public:
+    /**
+     * A temporary instance of this class can be used to select between moving an existing paint or
+     * a temporary copy of an existing paint into a call site. MoveOrClone(paint, false) is a rvalue
+     * reference to paint while MoveOrClone(paint, true) is a rvalue reference to a copy of paint.
+     */
+    using MoveOrClone = MoveOrImpl<true>;
+
+    /**
+     * A temporary instance of this class can be used to select between moving an existing or a
+     * newly default constructed paint into a call site. MoveOrNew(paint, false) is a rvalue
+     * reference to paint while MoveOrNew(paint, true) is a rvalue reference to a default paint.
+     */
+    using MoveOrNew = MoveOrImpl<false>;
+
+private:
+    GrPaint& operator=(const GrPaint&) = delete;
+
+    friend class GrPipelineBuilder;
+
     bool internalIsConstantBlendedColor(GrColor paintColor, GrColor* constantColor) const;
 
-    mutable sk_sp<GrXPFactory>                fXPFactory;
+    const GrXPFactory* fXPFactory = nullptr;
     SkSTArray<4, sk_sp<GrFragmentProcessor>>  fColorFragmentProcessors;
     SkSTArray<2, sk_sp<GrFragmentProcessor>>  fCoverageFragmentProcessors;
+    bool fDisableOutputConversionToSRGB = false;
+    bool fAllowSRGBInputs = false;
+    bool fUsesDistanceVectorField = false;
+    GrColor4f fColor = GrColor4f::OpaqueWhite();
+};
 
-    bool                                      fDisableOutputConversionToSRGB;
-    bool                                      fAllowSRGBInputs;
-    bool                                      fUsesDistanceVectorField;
+/** This is the implementation of MoveOrCopy and MoveOrNew. */
+template <bool COPY_IF_NEW>
+class GrPaint::MoveOrImpl {
+public:
+    MoveOrImpl(GrPaint& paint, bool newPaint) {
+        if (newPaint) {
+            if (COPY_IF_NEW) {
+                fStorage.init(paint);
+            } else {
+                fStorage.init();
+            };
+            fPaint = fStorage.get();
+        } else {
+            fPaint = &paint;
+        }
+    }
 
-    GrColor4f                                 fColor;
+    operator GrPaint&&() { return std::move(*fPaint); }
+
+private:
+    SkTLazy<GrPaint> fStorage;
+    GrPaint* fPaint;
 };
 
 #endif
diff --git a/include/gpu/GrProcessor.h b/include/gpu/GrProcessor.h
index 95a669e..32995c3 100644
--- a/include/gpu/GrProcessor.h
+++ b/include/gpu/GrProcessor.h
@@ -59,7 +59,7 @@
     Dynamically allocated GrProcessors are managed by a per-thread memory pool. The ref count of an
     processor must reach 0 before the thread terminates and the pool is destroyed.
  */
-class GrProcessor : public GrProgramElement {
+class GrProcessor : public GrProgramElement<GrProcessor> {
 public:
     class TextureSampler;
     class BufferAccess;
@@ -171,6 +171,11 @@
         return id;
     }
 
+    friend class GrProgramElement<GrProcessor>;
+    void addPendingIOs() const;
+    void removeRefs() const;
+    void pendingIOComplete() const;
+
     enum {
         kIllegalProcessorClassID = 0,
     };
@@ -247,6 +252,11 @@
  */
 class GrProcessor::BufferAccess : public SkNoncopyable {
 public:
+    BufferAccess() = default;
+    BufferAccess(GrPixelConfig texelConfig, GrBuffer* buffer,
+                 GrShaderFlags visibility = kFragment_GrShaderFlag) {
+        this->reset(texelConfig, buffer, visibility);
+    }
     /**
      * Must be initialized before adding to a GrProcessor's buffer access list.
      */
diff --git a/include/gpu/GrProcessorUnitTest.h b/include/gpu/GrProcessorUnitTest.h
index d398aae..0826e3d 100644
--- a/include/gpu/GrProcessorUnitTest.h
+++ b/include/gpu/GrProcessorUnitTest.h
@@ -18,6 +18,7 @@
 class GrRenderTargetContext;
 struct GrProcessorTestData;
 class GrTexture;
+class GrXPFactory;
 
 namespace GrProcessorUnitTest {
 
@@ -65,7 +66,7 @@
 class GrProcessor;
 class GrTexture;
 
-template <class Processor> class GrProcessorTestFactory : SkNoncopyable {
+template <class Processor> class GrProcessorTestFactory : private SkNoncopyable {
 public:
     typedef sk_sp<Processor> (*MakeProc)(GrProcessorTestData*);
 
@@ -88,20 +89,44 @@
     /** Use factory function at Index idx to create a processor. */
     static sk_sp<Processor> MakeIdx(int idx, GrProcessorTestData* data) {
         GrProcessorTestFactory<Processor>* factory = (*GetFactories())[idx];
-        return factory->fMakeProc(data);
+        sk_sp<Processor> processor = factory->fMakeProc(data);
+        SkASSERT(processor);
+        return processor;
     }
 
-    /*
+private:
+    /**
      * A test function which verifies the count of factories.
      */
     static void VerifyFactoryCount();
 
-private:
     MakeProc fMakeProc;
 
     static SkTArray<GrProcessorTestFactory<Processor>*, true>* GetFactories();
 };
 
+class GrXPFactoryTestFactory : private SkNoncopyable {
+public:
+    using GetFn = const GrXPFactory*(GrProcessorTestData*);
+
+    GrXPFactoryTestFactory(GetFn* getProc) : fGetProc(getProc) { GetFactories()->push_back(this); }
+
+    static const GrXPFactory* Get(GrProcessorTestData* data) {
+        VerifyFactoryCount();
+        SkASSERT(GetFactories()->count());
+        uint32_t idx = data->fRandom->nextRangeU(0, GetFactories()->count() - 1);
+        const GrXPFactory* xpf = (*GetFactories())[idx]->fGetProc(data);
+        SkASSERT(xpf);
+        return xpf;
+    }
+
+private:
+    static void VerifyFactoryCount();
+
+    GetFn* fGetProc;
+    static SkTArray<GrXPFactoryTestFactory*, true>* GetFactories();
+};
+
 /** GrProcessor subclasses should insert this macro in their declaration to be included in the
  *  program generation unit test.
  */
@@ -114,21 +139,21 @@
     static sk_sp<GrFragmentProcessor> TestCreate(GrProcessorTestData*)
 
 #define GR_DECLARE_XP_FACTORY_TEST                                                                 \
-    static GrProcessorTestFactory<GrXPFactory> gTestFactory SK_UNUSED;                             \
-    static sk_sp<GrXPFactory> TestCreate(GrProcessorTestData*)
+    static GrXPFactoryTestFactory gTestFactory SK_UNUSED;                                          \
+    static const GrXPFactory* TestGet(GrProcessorTestData*)
 
 /** GrProcessor subclasses should insert this macro in their implementation file. They must then
  *  also implement this static function:
  *      GrProcessor* TestCreate(GrProcessorTestData*);
  */
 #define GR_DEFINE_FRAGMENT_PROCESSOR_TEST(Effect)                                                  \
-    GrProcessorTestFactory<GrFragmentProcessor> Effect :: gTestFactory(Effect :: TestCreate)
-
-#define GR_DEFINE_XP_FACTORY_TEST(Factory)                                                         \
-    GrProcessorTestFactory<GrXPFactory> Factory :: gTestFactory(Factory :: TestCreate)
+    GrProcessorTestFactory<GrFragmentProcessor> Effect::gTestFactory(Effect::TestCreate)
 
 #define GR_DEFINE_GEOMETRY_PROCESSOR_TEST(Effect)                                                  \
-    GrProcessorTestFactory<GrGeometryProcessor> Effect :: gTestFactory(Effect :: TestCreate)
+    GrProcessorTestFactory<GrGeometryProcessor> Effect::gTestFactory(Effect::TestCreate)
+
+#define GR_DEFINE_XP_FACTORY_TEST(Factory)                                                         \
+    GrXPFactoryTestFactory Factory::gTestFactory(Factory::TestGet)
 
 #else // !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
 
@@ -140,15 +165,15 @@
 
 // The unit test relies on static initializers. Just declare the TestCreate function so that
 // its definitions will compile.
-#define GR_DECLARE_XP_FACTORY_TEST                                                                 \
-    static sk_sp<GrXPFactory> TestCreate(GrProcessorTestData*)
-#define GR_DEFINE_XP_FACTORY_TEST(X)
-
-// The unit test relies on static initializers. Just declare the TestCreate function so that
-// its definitions will compile.
 #define GR_DECLARE_GEOMETRY_PROCESSOR_TEST                                                         \
     static sk_sp<GrGeometryProcessor> TestCreate(GrProcessorTestData*)
 #define GR_DEFINE_GEOMETRY_PROCESSOR_TEST(X)
 
+// The unit test relies on static initializers. Just declare the TestGet function so that
+// its definitions will compile.
+#define GR_DECLARE_XP_FACTORY_TEST                                                                 \
+    const GrXPFactory* TestGet(GrProcessorTestData*)
+#define GR_DEFINE_XP_FACTORY_TEST(X)
+
 #endif // !SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
 #endif
diff --git a/include/gpu/GrProgramElement.h b/include/gpu/GrProgramElement.h
index ce09762..71905ac 100644
--- a/include/gpu/GrProgramElement.h
+++ b/include/gpu/GrProgramElement.h
@@ -14,6 +14,9 @@
 class GrGpuResourceRef;
 
 /**
+ * Note: We are converting GrProcessor from ref counting to a single owner model using move
+ * semantics. This class will be removed.
+ *
  * Base class for GrProcessor. This exists to manage transitioning a GrProcessor from being owned by
  * a client to being scheduled for execution. While a GrProcessor is ref'ed by drawing code its
  * GrGpu resources must also be ref'ed to prevent incorrectly recycling them through the cache.
@@ -30,18 +33,13 @@
  * safe to recycle a resource even though we still have buffered GrOps that read or write to the
  * the resource.
  *
- * To make this work all GrGpuResource objects owned by a GrProgramElement or derived classes
- * (either directly or indirectly) must be wrapped in a GrGpuResourceRef and registered with the
- * GrProgramElement using addGpuResource(). This allows the regular refs to be converted to pending
- * IO events when the program element is scheduled for deferred execution.
- *
- * Moreover, a GrProgramElement that in turn owns other GrProgramElements must convert its ownership
- * of its children to pending executions when its ref count reaches zero so that the GrGpuResources
- * owned by the children GrProgramElements are correctly converted from ownership by ref to
- * ownership by pending IO. Any GrProgramElement hierarchy is managed by subclasses which must
- * implement notifyRefCntIsZero() in order to convert refs of children to pending executions.
+ * To make this work the subclass, GrProcessor, implements addPendingIOs and pendingIOComplete. The
+ * former adds pending reads/writes as appropriate when the processor is recorded in a GrOpList. The
+ * latter removes them after the op list executes the operation. These calls must propagate to any
+ * children processors. Similarly, the subclass implements a removeRefs function in order to remove
+ * refs from resources once the processor is only owned for pending execution.
  */
-class GrProgramElement : public SkNoncopyable {
+template<typename DERIVED> class GrProgramElement : public SkNoncopyable {
 public:
     virtual ~GrProgramElement() {
         // fRefCnt can be one when an effect is created statically using GR_CREATE_STATIC_EFFECT
@@ -67,17 +65,12 @@
                 delete this;
                 return;
             } else {
-                this->removeRefs();
+                static_cast<const DERIVED*>(this)->removeRefs();
             }
         }
         this->validate();
     }
 
-    /**
-     * Gets an id that is unique for this GrProgramElement object. This will never return 0.
-     */
-    uint32_t getUniqueID() const { return fUniqueID; }
-
     void validate() const {
 #ifdef SK_DEBUG
         SkASSERT(fRefCnt >= 0);
@@ -87,21 +80,13 @@
     }
 
 protected:
-    GrProgramElement() : fRefCnt(1), fPendingExecutions(0), fUniqueID(CreateUniqueID()) {}
-
-    /** Subclasses registers their resources using this function. It is assumed the GrProgramResouce
-        is and will remain owned by the subclass and this function will retain a raw ptr. Once a
-        GrGpuResourceRef is registered its setResource must not be called.
-     */
-    void addGpuResource(const GrGpuResourceRef* res) {
-        fGpuResources.push_back(res);
-    }
+    GrProgramElement() : fRefCnt(1), fPendingExecutions(0) {}
 
     void addPendingExecution() const {
         this->validate();
         SkASSERT(fRefCnt > 0);
         if (0 == fPendingExecutions) {
-            this->addPendingIOs();
+            static_cast<const DERIVED*>(this)->addPendingIOs();
         }
         ++fPendingExecutions;
         this->validate();
@@ -115,7 +100,7 @@
                 delete this;
                 return;
             } else {
-                this->pendingIOComplete();
+                static_cast<const DERIVED*>(this)->pendingIOComplete();
             }
         }
         this->validate();
@@ -126,18 +111,9 @@
         executions. */
     virtual void notifyRefCntIsZero() const = 0;
 
-    static uint32_t CreateUniqueID();
-
-    void removeRefs() const;
-    void addPendingIOs() const;
-    void pendingIOComplete() const;
-
     mutable int32_t fRefCnt;
     // Count of deferred executions not yet issued to the 3D API.
     mutable int32_t fPendingExecutions;
-    uint32_t        fUniqueID;
-
-    SkSTArray<4, const GrGpuResourceRef*, true> fGpuResources;
 
     // Only this class can access addPendingExecution() and completedExecution().
     template <typename T> friend class GrPendingProgramElement;
diff --git a/include/gpu/GrRenderTargetContext.h b/include/gpu/GrRenderTargetContext.h
index b86b626..b16e6c7 100644
--- a/include/gpu/GrRenderTargetContext.h
+++ b/include/gpu/GrRenderTargetContext.h
@@ -53,13 +53,13 @@
     // TODO: it is odd that we need both the SkPaint in the following 3 methods.
     // We should extract the text parameters from SkPaint and pass them separately
     // akin to GrStyle (GrTextInfo?)
-    virtual void drawText(const GrClip&,  const GrPaint&, const SkPaint&,
-                          const SkMatrix& viewMatrix, const char text[], size_t byteLength,
-                          SkScalar x, SkScalar y, const SkIRect& clipBounds);
-    virtual void drawPosText(const GrClip&, const GrPaint&, const SkPaint&,
-                             const SkMatrix& viewMatrix, const char text[], size_t byteLength,
-                             const SkScalar pos[], int scalarsPerPosition,
-                             const SkPoint& offset, const SkIRect& clipBounds);
+    virtual void drawText(const GrClip&, GrPaint&&, const SkPaint&, const SkMatrix& viewMatrix,
+                          const char text[], size_t byteLength, SkScalar x, SkScalar y,
+                          const SkIRect& clipBounds);
+    virtual void drawPosText(const GrClip&, GrPaint&&, const SkPaint&, const SkMatrix& viewMatrix,
+                             const char text[], size_t byteLength, const SkScalar pos[],
+                             int scalarsPerPosition, const SkPoint& offset,
+                             const SkIRect& clipBounds);
     virtual void drawTextBlob(const GrClip&, const SkPaint&,
                               const SkMatrix& viewMatrix, const SkTextBlob*,
                               SkScalar x, SkScalar y,
@@ -83,7 +83,7 @@
     /**
      *  Draw everywhere (respecting the clip) with the paint.
      */
-    void drawPaint(const GrClip&, const GrPaint&, const SkMatrix& viewMatrix);
+    void drawPaint(const GrClip&, GrPaint&&, const SkMatrix& viewMatrix);
 
     /**
      * Draw the rect using a paint.
@@ -95,11 +95,11 @@
      * The rects coords are used to access the paint (through texture matrix)
      */
     void drawRect(const GrClip&,
-                  const GrPaint& paint,
+                  GrPaint&& paint,
                   GrAA,
                   const SkMatrix& viewMatrix,
                   const SkRect&,
-                  const GrStyle* style  = nullptr);
+                  const GrStyle* style = nullptr);
 
     /**
      * Maps a rectangle of shader coordinates to a rectangle and fills that rectangle.
@@ -111,7 +111,7 @@
      * @param localRect    the rectangle of shader coordinates applied to rectToDraw
      */
     void fillRectToRect(const GrClip&,
-                        const GrPaint& paint,
+                        GrPaint&& paint,
                         GrAA,
                         const SkMatrix& viewMatrix,
                         const SkRect& rectToDraw,
@@ -121,7 +121,7 @@
      * Fills a rect with a paint and a localMatrix.
      */
     void fillRectWithLocalMatrix(const GrClip& clip,
-                                 const GrPaint& paint,
+                                 GrPaint&& paint,
                                  GrAA,
                                  const SkMatrix& viewMatrix,
                                  const SkRect& rect,
@@ -137,7 +137,7 @@
      * @param style       style to apply to the rrect. Currently path effects are not allowed.
      */
     void drawRRect(const GrClip&,
-                   const GrPaint&,
+                   GrPaint&&,
                    GrAA,
                    const SkMatrix& viewMatrix,
                    const SkRRect& rrect,
@@ -154,7 +154,7 @@
      * @param style        style to apply to the rrect. Currently path effects are not allowed.
      */
     void drawShadowRRect(const GrClip&,
-                         const GrPaint&,
+                         GrPaint&&,
                          const SkMatrix& viewMatrix,
                          const SkRRect& rrect,
                          SkScalar blurRadius,
@@ -171,7 +171,7 @@
      * @param inner        the inner roundrect
      */
     void drawDRRect(const GrClip&,
-                    const GrPaint&,
+                    GrPaint&&,
                     GrAA,
                     const SkMatrix& viewMatrix,
                     const SkRRect& outer,
@@ -187,7 +187,7 @@
      * @param style         style to apply to the path.
      */
     void drawPath(const GrClip&,
-                  const GrPaint&,
+                  GrPaint&&,
                   GrAA,
                   const SkMatrix& viewMatrix,
                   const SkPath&,
@@ -211,7 +211,7 @@
      *                          number of indices.
      */
     void drawVertices(const GrClip&,
-                      const GrPaint& paint,
+                      GrPaint&& paint,
                       const SkMatrix& viewMatrix,
                       GrPrimitiveType primitiveType,
                       int vertexCount,
@@ -234,7 +234,7 @@
      *                          the paint's color field.
      */
     void drawAtlas(const GrClip&,
-                   const GrPaint& paint,
+                   GrPaint&& paint,
                    const SkMatrix& viewMatrix,
                    int spriteCount,
                    const SkRSXform xform[],
@@ -251,7 +251,7 @@
      * @param style         style to apply to the region
      */
     void drawRegion(const GrClip&,
-                    const GrPaint& paint,
+                    GrPaint&& paint,
                     GrAA aa,
                     const SkMatrix& viewMatrix,
                     const SkRegion& region,
@@ -267,7 +267,7 @@
      * @param style         style to apply to the oval. Currently path effects are not allowed.
      */
     void drawOval(const GrClip&,
-                  const GrPaint& paint,
+                  GrPaint&& paint,
                   GrAA,
                   const SkMatrix& viewMatrix,
                   const SkRect& oval,
@@ -288,7 +288,7 @@
      * @param style         style to apply to the oval.
      */
     void drawArc(const GrClip&,
-                 const GrPaint& paint,
+                 GrPaint&& paint,
                  GrAA,
                  const SkMatrix& viewMatrix,
                  const SkRect& oval,
@@ -301,7 +301,7 @@
      * Draw the image as a set of rects, specified by |iter|.
      */
     void drawImageLattice(const GrClip&,
-                          const GrPaint& paint,
+                          GrPaint&& paint,
                           const SkMatrix& viewMatrix,
                           int imageWidth,
                           int imageHeight,
@@ -437,22 +437,24 @@
 
     void internalClear(const GrFixedClip&, const GrColor, bool canIgnoreClip);
 
+    // Only consumes the GrPaint if successful.
     bool drawFilledDRRect(const GrClip& clip,
-                          const GrPaint& paint,
+                          GrPaint&& paint,
                           GrAA,
                           const SkMatrix& viewMatrix,
                           const SkRRect& origOuter,
                           const SkRRect& origInner);
 
+    // Only consumes the GrPaint if successful.
     bool drawFilledRect(const GrClip& clip,
-                        const GrPaint& paint,
+                        GrPaint&& paint,
                         GrAA,
                         const SkMatrix& viewMatrix,
                         const SkRect& rect,
                         const GrUserStencilSettings* ss);
 
     void drawNonAAFilledRect(const GrClip&,
-                             const GrPaint&,
+                             GrPaint&&,
                              const SkMatrix& viewMatrix,
                              const SkRect& rect,
                              const SkRect* localRect,
@@ -460,17 +462,13 @@
                              const GrUserStencilSettings* ss,
                              GrAAType hwOrNoneAAType);
 
-    void internalDrawPath(const GrClip&,
-                          const GrPaint&,
-                          GrAA,
-                          const SkMatrix&,
-                          const SkPath&,
-                          const GrStyle&);
+    void internalDrawPath(
+            const GrClip&, GrPaint&&, GrAA, const SkMatrix&, const SkPath&, const GrStyle&);
 
     bool onCopy(GrSurfaceProxy* src, const SkIRect& srcRect, const SkIPoint& dstPoint) override;
 
     // This entry point allows the GrTextContext-derived classes to add their ops to the GrOpList.
-    void addDrawOp(const GrPipelineBuilder&, const GrClip&, sk_sp<GrDrawOp>);
+    void addDrawOp(const GrPipelineBuilder&, const GrClip&, std::unique_ptr<GrDrawOp>);
 
     GrRenderTargetOpList* getOpList();
 
diff --git a/include/gpu/GrResourceKey.h b/include/gpu/GrResourceKey.h
index 0ead35e..8ba1140 100644
--- a/include/gpu/GrResourceKey.h
+++ b/include/gpu/GrResourceKey.h
@@ -9,10 +9,11 @@
 #ifndef GrResourceKey_DEFINED
 #define GrResourceKey_DEFINED
 
+#include "../private/SkOnce.h"
 #include "../private/SkTemplates.h"
 #include "GrTypes.h"
 #include "SkData.h"
-#include "../private/SkOnce.h"
+#include "SkString.h"
 
 uint32_t GrResourceKeyHash(const uint32_t* data, size_t size);
 
@@ -239,6 +240,7 @@
     GrUniqueKey& operator=(const GrUniqueKey& that) {
         this->INHERITED::operator=(that);
         this->setCustomData(sk_ref_sp(that.getCustomData()));
+        SkDEBUGCODE(fTag = that.fTag;)
         return *this;
     }
 
@@ -254,21 +256,28 @@
         return fData.get();
     }
 
+    SkDEBUGCODE(const char* tag() const { return fTag.c_str(); })
+
     class Builder : public INHERITED::Builder {
     public:
-        Builder(GrUniqueKey* key, Domain domain, int data32Count)
-            : INHERITED::Builder(key, domain, data32Count) {}
+        Builder(GrUniqueKey* key, Domain type, int data32Count, const char* tag = nullptr)
+                : INHERITED::Builder(key, type, data32Count) {
+            SkDEBUGCODE(key->fTag = tag;)
+            (void) tag;  // suppress unused named param warning.
+        }
 
         /** Used to build a key that wraps another key and adds additional data. */
-        Builder(GrUniqueKey* key, const GrUniqueKey& innerKey, Domain domain,
-                int extraData32Cnt)
-            : INHERITED::Builder(key, domain, Data32CntForInnerKey(innerKey) + extraData32Cnt) {
+        Builder(GrUniqueKey* key, const GrUniqueKey& innerKey, Domain domain, int extraData32Cnt,
+                const char* tag = nullptr)
+                : INHERITED::Builder(key, domain, Data32CntForInnerKey(innerKey) + extraData32Cnt) {
             SkASSERT(&innerKey != key);
             // add the inner key to the end of the key so that op[] can be indexed normally.
             uint32_t* innerKeyData = &this->operator[](extraData32Cnt);
             const uint32_t* srcData = innerKey.data();
             (*innerKeyData++) = innerKey.domain();
             memcpy(innerKeyData, srcData, innerKey.dataSize());
+            SkDEBUGCODE(key->fTag = tag;)
+            (void) tag;  // suppress unused named param warning.
         }
 
     private:
@@ -280,6 +289,7 @@
 
 private:
     sk_sp<SkData> fData;
+    SkDEBUGCODE(SkString fTag;)
 };
 
 /**
diff --git a/include/gpu/GrSurface.h b/include/gpu/GrSurface.h
index 2205bd8..7bfed3c 100644
--- a/include/gpu/GrSurface.h
+++ b/include/gpu/GrSurface.h
@@ -67,7 +67,32 @@
     virtual const GrRenderTarget* asRenderTarget() const { return NULL; }
 
     /**
-     * Reads a rectangle of pixels from the surface.
+     * Reads a rectangle of pixels from the surface, possibly performing color space conversion.
+     * @param srcColorSpace color space of the source data (this surface)
+     * @param left          left edge of the rectangle to read (inclusive)
+     * @param top           top edge of the rectangle to read (inclusive)
+     * @param width         width of rectangle to read in pixels.
+     * @param height        height of rectangle to read in pixels.
+     * @param config        the pixel config of the destination buffer
+     * @param dstColorSpace color space of the destination buffer
+     * @param buffer        memory to read the rectangle into.
+     * @param rowBytes      number of bytes between consecutive rows. Zero means rows are tightly
+     *                      packed.
+     * @param pixelOpsFlags See the GrContext::PixelOpsFlags enum.
+     *
+     * @return true if the read succeeded, false if not. The read can fail because of an unsupported
+     *              pixel config.
+     */
+    bool readPixels(SkColorSpace* srcColorSpace,
+                    int left, int top, int width, int height,
+                    GrPixelConfig config,
+                    SkColorSpace* dstColorSpace,
+                    void* buffer,
+                    size_t rowBytes = 0,
+                    uint32_t pixelOpsFlags = 0);
+
+    /**
+     * Reads a rectangle of pixels from the surface. Does not perform any color space conversion.
      * @param left          left edge of the rectangle to read (inclusive)
      * @param top           top edge of the rectangle to read (inclusive)
      * @param width         width of rectangle to read in pixels.
@@ -85,11 +110,40 @@
                     GrPixelConfig config,
                     void* buffer,
                     size_t rowBytes = 0,
-                    uint32_t pixelOpsFlags = 0);
+                    uint32_t pixelOpsFlags = 0) {
+        return this->readPixels(nullptr, left, top, width, height, config, nullptr, buffer,
+                                rowBytes, pixelOpsFlags);
+    }
 
     /**
      * Copy the src pixels [buffer, rowbytes, pixelconfig] into the surface at the specified
-     * rectangle.
+     * rectangle, possibly performing color space conversion.
+     * @param dstColorSpace color space of the destination (this surface)
+     * @param left          left edge of the rectangle to write (inclusive)
+     * @param top           top edge of the rectangle to write (inclusive)
+     * @param width         width of rectangle to write in pixels.
+     * @param height        height of rectangle to write in pixels.
+     * @param config        the pixel config of the source buffer
+     * @param srcColorSpace color space of the source buffer
+     * @param buffer        memory to read the rectangle from.
+     * @param rowBytes      number of bytes between consecutive rows. Zero means rows are tightly
+     *                      packed.
+     * @param pixelOpsFlags See the GrContext::PixelOpsFlags enum.
+     *
+     * @return true if the write succeeded, false if not. The write can fail because of an
+     *              unsupported pixel config.
+     */
+    bool writePixels(SkColorSpace* dstColorSpace,
+                     int left, int top, int width, int height,
+                     GrPixelConfig config,
+                     SkColorSpace* srcColorSpace,
+                     const void* buffer,
+                     size_t rowBytes = 0,
+                     uint32_t pixelOpsFlags = 0);
+
+    /**
+     * Copy the src pixels [buffer, rowbytes, pixelconfig] into the surface at the specified
+     * rectangle. Does not perform any color space conversion.
      * @param left          left edge of the rectangle to write (inclusive)
      * @param top           top edge of the rectangle to write (inclusive)
      * @param width         width of rectangle to write in pixels.
@@ -107,7 +161,10 @@
                      GrPixelConfig config,
                      const void* buffer,
                      size_t rowBytes = 0,
-                     uint32_t pixelOpsFlags = 0);
+                     uint32_t pixelOpsFlags = 0) {
+        return this->writePixels(nullptr, left, top, width, height, config, nullptr, buffer,
+                                 rowBytes, pixelOpsFlags);
+    }
 
     /**
      * After this returns any pending writes to the surface will be issued to the backend 3D API.
diff --git a/include/gpu/GrTypes.h b/include/gpu/GrTypes.h
index 86e83f9..b3f1c19 100644
--- a/include/gpu/GrTypes.h
+++ b/include/gpu/GrTypes.h
@@ -219,7 +219,7 @@
 enum GrPixelConfig {
     kUnknown_GrPixelConfig,
     kAlpha_8_GrPixelConfig,
-    kIndex_8_GrPixelConfig,
+    kGray_8_GrPixelConfig,
     kRGB_565_GrPixelConfig,
     /**
      * Premultiplied
@@ -306,7 +306,6 @@
 // representation.
 static inline bool GrPixelConfigIsCompressed(GrPixelConfig config) {
     switch (config) {
-        case kIndex_8_GrPixelConfig:
         case kETC1_GrPixelConfig:
         case kLATC_GrPixelConfig:
         case kR11_EAC_GrPixelConfig:
@@ -320,7 +319,6 @@
 /** If the pixel config is compressed, return an equivalent uncompressed format. */
 static inline GrPixelConfig GrMakePixelConfigUncompressed(GrPixelConfig config) {
     switch (config) {
-        case kIndex_8_GrPixelConfig:
         case kETC1_GrPixelConfig:
         case kASTC_12x12_GrPixelConfig:
             return kRGBA_8888_GrPixelConfig;
@@ -379,6 +377,7 @@
     SkASSERT(!GrPixelConfigIsCompressed(config));
     switch (config) {
         case kAlpha_8_GrPixelConfig:
+        case kGray_8_GrPixelConfig:
             return 1;
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
@@ -403,6 +402,7 @@
     switch (config) {
         case kETC1_GrPixelConfig:
         case kRGB_565_GrPixelConfig:
+        case kGray_8_GrPixelConfig:
             return true;
         default:
             return false;
@@ -656,11 +656,8 @@
 static inline size_t GrCompressedFormatDataSize(GrPixelConfig config,
                                                 int width, int height) {
     SkASSERT(GrPixelConfigIsCompressed(config));
-    static const int kGrIndex8TableSize = 256 * 4; // 4 == sizeof(GrColor)
 
     switch (config) {
-        case kIndex_8_GrPixelConfig:
-            return width * height + kGrIndex8TableSize;
         case kR11_EAC_GrPixelConfig:
         case kLATC_GrPixelConfig:
         case kETC1_GrPixelConfig:
diff --git a/include/gpu/GrXferProcessor.h b/include/gpu/GrXferProcessor.h
index d5fa9e0..17cd2c7 100644
--- a/include/gpu/GrXferProcessor.h
+++ b/include/gpu/GrXferProcessor.h
@@ -113,17 +113,13 @@
          */
         kIgnoreColor_OptFlag = 0x2,
         /**
-         * GrXferProcessor will ignore coverage, thus no need to provide
-         */
-        kIgnoreCoverage_OptFlag = 0x4,
-        /**
          * Clear color stages and override input color to that returned by getOptimizations
          */
-        kOverrideColor_OptFlag = 0x8,
+        kOverrideColor_OptFlag = 0x4,
         /**
          * Can tweak alpha for coverage. Currently this flag should only be used by a GrDrawOp.
          */
-        kCanTweakAlphaForCoverage_OptFlag = 0x20,
+        kCanTweakAlphaForCoverage_OptFlag = 0x8,
     };
 
     static const OptFlags kNone_OptFlags = (OptFlags)0;
@@ -293,8 +289,22 @@
  * Before the XP is created, the XPF is able to answer queries about what functionality the XPs it
  * creates will have. For example, can it create an XP that supports RGB coverage or will the XP
  * blend with the destination color.
+ *
+ * GrXPFactories are intended to be static immutable objects. We pass them around as raw pointers
+ * and expect the pointers to always be valid and for the factories to be reusable and thread safe.
+ * Equality is tested for using pointer comparison. GrXPFactory destructors must be no-ops.
  */
-class GrXPFactory : public SkRefCnt {
+
+// In order to construct GrXPFactory subclass instances as constexpr the subclass, and therefore
+// GrXPFactory, must be a literal type. One requirement is having a trivial destructor. This is ok
+// since these objects have no need for destructors. However, GCC and clang throw a warning when a
+// class has virtual functions and a non-virtual destructor. We suppress that warning here and
+// for the subclasses.
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+class GrXPFactory {
 public:
     typedef GrXferProcessor::DstTexture DstTexture;
     GrXferProcessor* createXferProcessor(const GrPipelineAnalysis&,
@@ -321,29 +331,8 @@
 
     bool willNeedDstTexture(const GrCaps& caps, const GrPipelineAnalysis&) const;
 
-    bool isEqual(const GrXPFactory& that) const {
-        if (this->classID() != that.classID()) {
-            return false;
-        }
-        return this->onIsEqual(that);
-    }
-
-    /**
-      * Helper for down-casting to a GrXPFactory subclass
-      */
-    template <typename T> const T& cast() const { return *static_cast<const T*>(this); }
-
-    uint32_t classID() const { SkASSERT(kIllegalXPFClassID != fClassID); return fClassID; }
-
 protected:
-    GrXPFactory() : fClassID(kIllegalXPFClassID) {}
-
-    template <typename XPF_SUBCLASS> void initClassID() {
-         static uint32_t kClassID = GenClassID();
-         fClassID = kClassID;
-    }
-
-    uint32_t fClassID;
+    constexpr GrXPFactory() {}
 
 private:
     virtual GrXferProcessor* onCreateXferProcessor(const GrCaps& caps,
@@ -351,34 +340,17 @@
                                                    bool hasMixedSamples,
                                                    const DstTexture*) const = 0;
 
-    virtual bool onIsEqual(const GrXPFactory&) const = 0;
-
     bool willReadDstColor(const GrCaps&, const GrPipelineAnalysis&) const;
+
     /**
      *  Returns true if the XP generated by this factory will explicitly read dst in the fragment
      *  shader.
      */
     virtual bool onWillReadDstColor(const GrCaps&, const GrPipelineAnalysis&) const = 0;
-
-    static uint32_t GenClassID() {
-        // fCurrXPFactoryID has been initialized to kIllegalXPFactoryID. 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(&gCurrXPFClassID)) + 1;
-        if (!id) {
-            SkFAIL("This should never wrap as it should only be called once for each GrXPFactory "
-                   "subclass.");
-        }
-        return id;
-    }
-
-    enum {
-        kIllegalXPFClassID = 0,
-    };
-    static int32_t gCurrXPFClassID;
-
-    typedef GrProgramElement INHERITED;
 };
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic pop
+#endif
 
 #endif
 
diff --git a/include/gpu/SkGr.h b/include/gpu/SkGr.h
index e59720e..e2f09e5 100644
--- a/include/gpu/SkGr.h
+++ b/include/gpu/SkGr.h
@@ -84,12 +84,7 @@
 sk_sp<GrTexture> GrMakeCachedBitmapTexture(GrContext*, const SkBitmap&, const GrSamplerParams&);
 
 // TODO: Move SkImageInfo2GrPixelConfig to SkGrPriv.h (requires cleanup to SkWindow its subclasses).
-GrPixelConfig SkImageInfo2GrPixelConfig(SkColorType, SkAlphaType, const SkColorSpace*,
-                                        const GrCaps&);
-
-static inline GrPixelConfig SkImageInfo2GrPixelConfig(const SkImageInfo& info, const GrCaps& caps) {
-    return SkImageInfo2GrPixelConfig(info.colorType(), info.alphaType(), info.colorSpace(), caps);
-}
+GrPixelConfig SkImageInfo2GrPixelConfig(const SkImageInfo& info, const GrCaps& caps);
 
 GrSamplerParams::FilterMode GrSkFilterQualityToGrFilterMode(SkFilterQuality paintFilterQuality,
                                                             const SkMatrix& viewM,
diff --git a/include/gpu/effects/GrCoverageSetOpXP.h b/include/gpu/effects/GrCoverageSetOpXP.h
index 2aae7be..ca71abc 100644
--- a/include/gpu/effects/GrCoverageSetOpXP.h
+++ b/include/gpu/effects/GrCoverageSetOpXP.h
@@ -14,6 +14,12 @@
 
 class GrProcOptInfo;
 
+// See the comment above GrXPFactory's definition about this warning suppression.
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
 /**
  * This xfer processor directly blends the the src coverage with the dst using a set operator. It is
  * useful for rendering coverage masks using CSG. It can optionally invert the src coverage before
@@ -21,13 +27,13 @@
  */
 class GrCoverageSetOpXPFactory : public GrXPFactory {
 public:
-    static sk_sp<GrXPFactory> Make(SkRegion::Op regionOp, bool invertCoverage = false);
+    static const GrXPFactory* Get(SkRegion::Op regionOp, bool invertCoverage = false);
 
     void getInvariantBlendedColor(const GrProcOptInfo& colorPOI,
                                   GrXPFactory::InvariantBlendedColor*) const override;
 
 private:
-    GrCoverageSetOpXPFactory(SkRegion::Op regionOp, bool invertCoverage);
+    constexpr GrCoverageSetOpXPFactory(SkRegion::Op regionOp, bool invertCoverage);
 
     GrXferProcessor* onCreateXferProcessor(const GrCaps&,
                                            const GrPipelineAnalysis&,
@@ -38,11 +44,6 @@
         return false;
     }
 
-    bool onIsEqual(const GrXPFactory& xpfBase) const override {
-        const GrCoverageSetOpXPFactory& xpf = xpfBase.cast<GrCoverageSetOpXPFactory>();
-        return fRegionOp == xpf.fRegionOp;
-    }
-
     GR_DECLARE_XP_FACTORY_TEST;
 
     SkRegion::Op fRegionOp;
@@ -50,5 +51,8 @@
 
     typedef GrXPFactory INHERITED;
 };
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic pop
+#endif
 #endif
 
diff --git a/include/gpu/effects/GrCustomXfermode.h b/include/gpu/effects/GrCustomXfermode.h
index a8c868e..54309dd 100644
--- a/include/gpu/effects/GrCustomXfermode.h
+++ b/include/gpu/effects/GrCustomXfermode.h
@@ -20,7 +20,7 @@
  */
 namespace GrCustomXfermode {
     bool IsSupportedMode(SkBlendMode mode);
-    sk_sp<GrXPFactory> MakeXPFactory(SkBlendMode mode);
+    const GrXPFactory* Get(SkBlendMode mode);
 };
 
 #endif
diff --git a/include/gpu/effects/GrPorterDuffXferProcessor.h b/include/gpu/effects/GrPorterDuffXferProcessor.h
index 29d56ab..ca14275 100644
--- a/include/gpu/effects/GrPorterDuffXferProcessor.h
+++ b/include/gpu/effects/GrPorterDuffXferProcessor.h
@@ -14,9 +14,14 @@
 
 class GrProcOptInfo;
 
+// See the comment above GrXPFactory's definition about this warning suppression.
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
 class GrPorterDuffXPFactory : public GrXPFactory {
 public:
-    static sk_sp<GrXPFactory> Make(SkBlendMode mode);
+    static const GrXPFactory* Get(SkBlendMode blendMode);
 
     void getInvariantBlendedColor(const GrProcOptInfo& colorPOI,
                                   GrXPFactory::InvariantBlendedColor*) const override;
@@ -51,7 +56,7 @@
     static bool SrcOverWillNeedDstTexture(const GrCaps&, const GrPipelineAnalysis&);
 
 private:
-    GrPorterDuffXPFactory(SkBlendMode);
+    constexpr GrPorterDuffXPFactory(SkBlendMode);
 
     GrXferProcessor* onCreateXferProcessor(const GrCaps& caps,
                                            const GrPipelineAnalysis&,
@@ -60,18 +65,16 @@
 
     bool onWillReadDstColor(const GrCaps&, const GrPipelineAnalysis&) const override;
 
-    bool onIsEqual(const GrXPFactory& xpfBase) const override {
-        const GrPorterDuffXPFactory& xpf = xpfBase.cast<GrPorterDuffXPFactory>();
-        return fXfermode == xpf.fXfermode;
-    }
-
     GR_DECLARE_XP_FACTORY_TEST;
     static void TestGetXPOutputTypes(const GrXferProcessor*, int* outPrimary, int* outSecondary);
 
-    SkBlendMode fXfermode;
+    SkBlendMode fBlendMode;
 
     friend class GrPorterDuffTest; // for TestGetXPOutputTypes()
     typedef GrXPFactory INHERITED;
 };
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic pop
+#endif
 
 #endif
diff --git a/include/images/SkForceLinking.h b/include/images/SkForceLinking.h
deleted file mode 100644
index ca52eeb..0000000
--- a/include/images/SkForceLinking.h
+++ /dev/null
@@ -1,7 +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.
- */
-// TODO(halcanary): delete this file.
diff --git a/include/ports/SkFontMgr.h b/include/ports/SkFontMgr.h
index afadeaa..4103e87 100644
--- a/include/ports/SkFontMgr.h
+++ b/include/ports/SkFontMgr.h
@@ -165,11 +165,8 @@
 
     SkTypeface* legacyCreateTypeface(const char familyName[], SkFontStyle style) const;
 
-    /**
-     *  Return a ref to the default fontmgr. The caller must call unref() on
-     *  the returned object.
-     */
-    static SkFontMgr* RefDefault();
+    /** Return the default fontmgr. */
+    static sk_sp<SkFontMgr> RefDefault();
 
 protected:
     virtual int onCountFamilies() const = 0;
@@ -197,7 +194,9 @@
     virtual SkTypeface* onLegacyCreateTypeface(const char familyName[], SkFontStyle) const = 0;
 
 private:
-    static SkFontMgr* Factory();    // implemented by porting layer
+
+    /** Implemented by porting layer to return the default factory. */
+    static sk_sp<SkFontMgr> Factory();
 
     typedef SkRefCnt INHERITED;
 };
diff --git a/include/ports/SkFontMgr_FontConfigInterface.h b/include/ports/SkFontMgr_FontConfigInterface.h
index 356e54c..9dccb7b 100644
--- a/include/ports/SkFontMgr_FontConfigInterface.h
+++ b/include/ports/SkFontMgr_FontConfigInterface.h
@@ -15,6 +15,6 @@
 class SkFontConfigInterface;
 
 /** Creates a SkFontMgr which wraps a SkFontConfigInterface. */
-SK_API SkFontMgr* SkFontMgr_New_FCI(sk_sp<SkFontConfigInterface> fci);
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_FCI(sk_sp<SkFontConfigInterface> fci);
 
 #endif // #ifndef SkFontMgr_FontConfigInterface_DEFINED
diff --git a/include/ports/SkFontMgr_android.h b/include/ports/SkFontMgr_android.h
index f12f51f..e1a8fec 100644
--- a/include/ports/SkFontMgr_android.h
+++ b/include/ports/SkFontMgr_android.h
@@ -12,13 +12,6 @@
 
 class SkFontMgr;
 
-/**
- *  For test only -- this only affects the default factory.
- *  Load font config from given xml files, instead of those from Android system.
- */
-SK_API void SkUseTestFontConfigFile(const char* mainconf, const char* fallbackconf,
-                                    const char* fontsdir);
-
 struct SkFontMgr_Android_CustomFonts {
     /** When specifying custom fonts, indicates how to use system fonts. */
     enum SystemFontUse {
@@ -47,6 +40,6 @@
 };
 
 /** Create a font manager for Android. If 'custom' is NULL, use only system fonts. */
-SK_API SkFontMgr* SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts* custom);
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts* custom);
 
 #endif // SkFontMgr_android_DEFINED
diff --git a/include/ports/SkFontMgr_custom.h b/include/ports/SkFontMgr_custom.h
index 53be63d..d918763 100644
--- a/include/ports/SkFontMgr_custom.h
+++ b/include/ports/SkFontMgr_custom.h
@@ -13,9 +13,9 @@
 class SkFontMgr;
 
 /** Create a custom font manager which scans a given directory for font files. */
-SK_API SkFontMgr* SkFontMgr_New_Custom_Directory(const char* dir);
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_Custom_Directory(const char* dir);
 
 /** Create a custom font manager that contains no built-in fonts. */
-SK_API SkFontMgr* SkFontMgr_New_Custom_Empty();
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_Custom_Empty();
 
 #endif // SkFontMgr_custom_DEFINED
diff --git a/include/ports/SkFontMgr_fontconfig.h b/include/ports/SkFontMgr_fontconfig.h
index 7a59ff0..75497cb 100644
--- a/include/ports/SkFontMgr_fontconfig.h
+++ b/include/ports/SkFontMgr_fontconfig.h
@@ -17,6 +17,6 @@
  *  If 'fc' is NULL, will use a new default config.
  *  Takes ownership of 'fc' and will call FcConfigDestroy on it.
  */
-SK_API SkFontMgr* SkFontMgr_New_FontConfig(FcConfig* fc);
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_FontConfig(FcConfig* fc);
 
 #endif // #ifndef SkFontMgr_fontconfig_DEFINED
diff --git a/include/ports/SkTypeface_win.h b/include/ports/SkTypeface_win.h
index f3a881f..06e7d33 100644
--- a/include/ports/SkTypeface_win.h
+++ b/include/ports/SkTypeface_win.h
@@ -44,12 +44,12 @@
 struct IDWriteFontCollection;
 struct IDWriteFontFallback;
 
-SK_API SkFontMgr* SkFontMgr_New_GDI();
-SK_API SkFontMgr* SkFontMgr_New_DirectWrite(IDWriteFactory* factory = NULL,
-                                            IDWriteFontCollection* collection = NULL);
-SK_API SkFontMgr* SkFontMgr_New_DirectWrite(IDWriteFactory* factory,
-                                            IDWriteFontCollection* collection,
-                                            IDWriteFontFallback* fallback);
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_GDI();
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_DirectWrite(IDWriteFactory* factory = NULL,
+                                                  IDWriteFontCollection* collection = NULL);
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_DirectWrite(IDWriteFactory* factory,
+                                                  IDWriteFontCollection* collection,
+                                                  IDWriteFontFallback* fallback);
 
 /**
  *  Creates an SkFontMgr which renders using DirectWrite and obtains its data
@@ -57,7 +57,7 @@
  *
  *  If DirectWrite could not be initialized, will return NULL.
  */
-SK_API SkFontMgr* SkFontMgr_New_DirectWriteRenderer(sk_sp<SkRemotableFontMgr>);
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_DirectWriteRenderer(sk_sp<SkRemotableFontMgr>);
 
 /**
  *  Creates an SkRemotableFontMgr backed by DirectWrite using the default
@@ -65,7 +65,7 @@
  *
  *  If DirectWrite could not be initialized, will return NULL.
  */
-SK_API SkRemotableFontMgr* SkRemotableFontMgr_New_DirectWrite();
+SK_API sk_sp<SkRemotableFontMgr> SkRemotableFontMgr_New_DirectWrite();
 
 #endif  // SK_BUILD_FOR_WIN
 #endif  // SkTypeface_win_DEFINED
diff --git a/include/private/GrAuditTrail.h b/include/private/GrAuditTrail.h
index 0fbbf6f..f1ae494 100644
--- a/include/private/GrAuditTrail.h
+++ b/include/private/GrAuditTrail.h
@@ -80,7 +80,7 @@
         fCurrentStackTrace.push_back(SkString(framename));
     }
 
-    void addOp(const GrOp*);
+    void addOp(const GrOp*, GrGpuResource::UniqueID renderTargetID);
 
     void opsCombined(const GrOp* consumer, const GrOp* consumed);
 
@@ -172,7 +172,8 @@
 #define GR_AUDIT_TRAIL_RESET(audit_trail) \
     //GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, fullReset);
 
-#define GR_AUDIT_TRAIL_ADD_OP(audit_trail, op) GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, addOp, op);
+#define GR_AUDIT_TRAIL_ADD_OP(audit_trail, op, rt_id) \
+    GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, addOp, op, rt_id);
 
 #define GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(audit_trail, combineWith, op) \
     GR_AUDIT_TRAIL_INVOKE_GUARD(audit_trail, opsCombined, combineWith, op);
diff --git a/include/private/GrSwizzle.h b/include/private/GrSwizzle.h
index 5fa39dd..4acf7e4 100644
--- a/include/private/GrSwizzle.h
+++ b/include/private/GrSwizzle.h
@@ -93,6 +93,7 @@
     static GrSwizzle RGBA() { return GrSwizzle("rgba"); }
     static GrSwizzle AAAA() { return GrSwizzle("aaaa"); }
     static GrSwizzle RRRR() { return GrSwizzle("rrrr"); }
+    static GrSwizzle RRRA() { return GrSwizzle("rrra"); }
     static GrSwizzle BGRA() { return GrSwizzle("bgra"); }
 
     static GrSwizzle CreateRandom(SkRandom* random) {
diff --git a/include/private/SkAutoMalloc.h b/include/private/SkAutoMalloc.h
new file mode 100644
index 0000000..7be8825
--- /dev/null
+++ b/include/private/SkAutoMalloc.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkAutoMalloc_DEFINED
+#define SkAutoMalloc_DEFINED
+
+#include "SkTypes.h"
+
+#include <memory>
+
+/**
+ *  Manage an allocated block of heap memory. This object is the sole manager of
+ *  the lifetime of the block, so the caller must not call sk_free() or delete
+ *  on the block, unless release() was called.
+ */
+class SkAutoMalloc : SkNoncopyable {
+public:
+    explicit SkAutoMalloc(size_t size = 0)
+        : fPtr(size ? sk_malloc_throw(size) : nullptr), fSize(size) {}
+
+    /**
+     *  Passed to reset to specify what happens if the requested size is smaller
+     *  than the current size (and the current block was dynamically allocated).
+     */
+    enum OnShrink {
+        /**
+         *  If the requested size is smaller than the current size, and the
+         *  current block is dynamically allocated, free the old block and
+         *  malloc a new block of the smaller size.
+         */
+        kAlloc_OnShrink,
+
+        /**
+         *  If the requested size is smaller than the current size, and the
+         *  current block is dynamically allocated, just return the old
+         *  block.
+         */
+        kReuse_OnShrink
+    };
+
+    /**
+     *  Reallocates the block to a new size. The ptr may or may not change.
+     */
+    void* reset(size_t size = 0, OnShrink shrink = kAlloc_OnShrink) {
+        if (size != fSize && (size > fSize || kReuse_OnShrink != shrink)) {
+            fPtr.reset(size ? sk_malloc_throw(size) : nullptr);
+            fSize = size;
+        }
+        return fPtr.get();
+    }
+
+    /**
+     *  Return the allocated block.
+     */
+    void* get() { return fPtr.get(); }
+    const void* get() const { return fPtr.get(); }
+
+   /** Transfer ownership of the current ptr to the caller, setting the
+       internal reference to null. Note the caller is reponsible for calling
+       sk_free on the returned address.
+    */
+    void* release() {
+        fSize = 0;
+        return fPtr.release();
+    }
+
+private:
+    struct WrapFree {
+        void operator()(void* p) { sk_free(p); }
+    };
+    std::unique_ptr<void, WrapFree> fPtr;
+    size_t fSize;  // can be larger than the requested size (see kReuse)
+};
+#define SkAutoMalloc(...) SK_REQUIRE_LOCAL_VAR(SkAutoMalloc)
+
+/**
+ *  Manage an allocated block of memory. If the requested size is <= kSizeRequested (or slightly
+ *  more), then the allocation will come from the stack rather than the heap. This object is the
+ *  sole manager of the lifetime of the block, so the caller must not call sk_free() or delete on
+ *  the block.
+ */
+template <size_t kSizeRequested> class SkAutoSMalloc : SkNoncopyable {
+public:
+    /**
+     *  Creates initially empty storage. get() returns a ptr, but it is to a zero-byte allocation.
+     *  Must call reset(size) to return an allocated block.
+     */
+    SkAutoSMalloc() {
+        fPtr = fStorage;
+        fSize = kSize;
+    }
+
+    /**
+     *  Allocate a block of the specified size. If size <= kSizeRequested (or slightly more), then
+     *  the allocation will come from the stack, otherwise it will be dynamically allocated.
+     */
+    explicit SkAutoSMalloc(size_t size) {
+        fPtr = fStorage;
+        fSize = kSize;
+        this->reset(size);
+    }
+
+    /**
+     *  Free the allocated block (if any). If the block was small enough to have been allocated on
+     *  the stack, then this does nothing.
+     */
+    ~SkAutoSMalloc() {
+        if (fPtr != (void*)fStorage) {
+            sk_free(fPtr);
+        }
+    }
+
+    /**
+     *  Return the allocated block. May return non-null even if the block is of zero size. Since
+     *  this may be on the stack or dynamically allocated, the caller must not call sk_free() on it,
+     *  but must rely on SkAutoSMalloc to manage it.
+     */
+    void* get() const { return fPtr; }
+
+    /**
+     *  Return a new block of the requested size, freeing (as necessary) any previously allocated
+     *  block. As with the constructor, if size <= kSizeRequested (or slightly more) then the return
+     *  block may be allocated locally, rather than from the heap.
+     */
+    void* reset(size_t size,
+                SkAutoMalloc::OnShrink shrink = SkAutoMalloc::kAlloc_OnShrink,
+                bool* didChangeAlloc = nullptr) {
+        size = (size < kSize) ? kSize : size;
+        bool alloc = size != fSize && (SkAutoMalloc::kAlloc_OnShrink == shrink || size > fSize);
+        if (didChangeAlloc) {
+            *didChangeAlloc = alloc;
+        }
+        if (alloc) {
+            if (fPtr != (void*)fStorage) {
+                sk_free(fPtr);
+            }
+
+            if (size == kSize) {
+                SkASSERT(fPtr != fStorage); // otherwise we lied when setting didChangeAlloc.
+                fPtr = fStorage;
+            } else {
+                fPtr = sk_malloc_flags(size, SK_MALLOC_THROW | SK_MALLOC_TEMP);
+            }
+
+            fSize = size;
+        }
+        SkASSERT(fSize >= size && fSize >= kSize);
+        SkASSERT((fPtr == fStorage) || fSize > kSize);
+        return fPtr;
+    }
+
+private:
+    // Align up to 32 bits.
+    static const size_t kSizeAlign4 = SkAlign4(kSizeRequested);
+#if defined(GOOGLE3)
+    // Stack frame size is limited for GOOGLE3. 4k is less than the actual max, but some functions
+    // have multiple large stack allocations.
+    static const size_t kMaxBytes = 4 * 1024;
+    static const size_t kSize = kSizeRequested > kMaxBytes ? kMaxBytes : kSizeAlign4;
+#else
+    static const size_t kSize = kSizeAlign4;
+#endif
+
+    void*       fPtr;
+    size_t      fSize;  // can be larger than the requested size (see kReuse)
+    uint32_t    fStorage[kSize >> 2];
+};
+// Can't guard the constructor because it's a template class.
+
+#endif
diff --git a/src/core/SkMessageBus.h b/include/private/SkMessageBus.h
similarity index 100%
rename from src/core/SkMessageBus.h
rename to include/private/SkMessageBus.h
diff --git a/include/private/SkTemplates.h b/include/private/SkTemplates.h
index 5c6403d..f50af8b 100644
--- a/include/private/SkTemplates.h
+++ b/include/private/SkTemplates.h
@@ -462,4 +462,6 @@
     SkAlignedSStorage<sizeof(T)*N> fStorage;
 };
 
+using SkAutoFree = std::unique_ptr<void, SkFunctionWrapper<void, void, sk_free>>;
+
 #endif
diff --git a/include/utils/mac/SkCGUtils.h b/include/utils/mac/SkCGUtils.h
index 592f2f6..2dcbb96 100644
--- a/include/utils/mac/SkCGUtils.h
+++ b/include/utils/mac/SkCGUtils.h
@@ -24,8 +24,11 @@
 
 class SkBitmap;
 class SkData;
+class SkPixmap;
 class SkStreamRewindable;
 
+SK_API CGContextRef SkCreateCGContext(const SkPixmap&);
+
 /**
  *  Given a CGImage, allocate an SkBitmap and copy the image's pixels into it. If scaleToFit is not
  *  null, use it to determine the size of the bitmap, and scale the image to fill the bitmap.
diff --git a/include/xml/SkDOM.h b/include/xml/SkDOM.h
index 7d7eafd..aa7ea78 100644
--- a/include/xml/SkDOM.h
+++ b/include/xml/SkDOM.h
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2006 The Android Open Source Project
  *
@@ -6,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-
 #ifndef SkDOM_DEFINED
 #define SkDOM_DEFINED
 
@@ -44,7 +42,7 @@
         kElement_Type,
         kText_Type
     };
-    Type    getType(const Node*) const;
+    Type getType(const Node*) const;
 
     const char* getName(const Node*) const;
     const Node* getFirstChild(const Node*, const char elem[] = NULL) const;
@@ -66,8 +64,7 @@
     bool findBool(const Node*, const char name[], bool*) const;
     int  findList(const Node*, const char name[], const char list[]) const;
 
-    bool findScalar(const Node* node, const char name[], SkScalar value[]) const
-    {
+    bool findScalar(const Node* node, const char name[], SkScalar value[]) const {
         return this->findScalars(node, name, value, 1);
     }
 
@@ -86,8 +83,6 @@
         const Attr* fStop;
     };
 
-    SkDEBUGCODE(void dump(const Node* node = NULL, int tabLevel = 0) const;)
-
 private:
     SkChunkAlloc                 fAlloc;
     Node*                        fRoot;
diff --git a/include/xml/SkXMLWriter.h b/include/xml/SkXMLWriter.h
index 3290126..b1d513c 100644
--- a/include/xml/SkXMLWriter.h
+++ b/include/xml/SkXMLWriter.h
@@ -66,7 +66,6 @@
     SkXMLStreamWriter(SkWStream*);
     virtual ~SkXMLStreamWriter();
     void writeHeader() override;
-    SkDEBUGCODE(static void UnitTest();)
 
 protected:
     void onStartElementLen(const char elem[], size_t length) override;
diff --git a/infra/bots/assets/skp/VERSION b/infra/bots/assets/skp/VERSION
index d99e90e..1758ddd 100644
--- a/infra/bots/assets/skp/VERSION
+++ b/infra/bots/assets/skp/VERSION
@@ -1 +1 @@
-29
\ No newline at end of file
+32
\ No newline at end of file
diff --git a/infra/bots/assets/win_toolchain/VERSION b/infra/bots/assets/win_toolchain/VERSION
index 7813681..62f9457 100644
--- a/infra/bots/assets/win_toolchain/VERSION
+++ b/infra/bots/assets/win_toolchain/VERSION
@@ -1 +1 @@
-5
\ No newline at end of file
+6
\ 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 4ec20d4..e96cde9 100755
--- a/infra/bots/assets/win_toolchain/create.py
+++ b/infra/bots/assets/win_toolchain/create.py
@@ -29,7 +29,6 @@
   'App Certification Kit',
   'Debuggers',
   'Extension SDKs',
-  'winrt',
   'DesignTime',
   'AccChecker',
 ]
diff --git a/infra/bots/gen_tasks.go b/infra/bots/gen_tasks.go
index f34c509..4456f17 100644
--- a/infra/bots/gen_tasks.go
+++ b/infra/bots/gen_tasks.go
@@ -67,6 +67,7 @@
 		"Build-Ubuntu-Clang-x86_64-Debug-ASAN",
 		"Build-Ubuntu-Clang-x86_64-Debug-MSAN",
 		"Build-Ubuntu-Clang-x86_64-Release",
+		"Build-Ubuntu-Clang-x86_64-Release-Fast",
 		"Build-Ubuntu-Clang-x86_64-Release-TSAN",
 		"Build-Ubuntu-GCC-x86-Debug",
 		"Build-Ubuntu-GCC-x86-Release",
@@ -75,7 +76,6 @@
 		"Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE",
 		"Build-Ubuntu-GCC-x86_64-Release",
 		"Build-Ubuntu-GCC-x86_64-Release-ANGLE",
-		"Build-Ubuntu-GCC-x86_64-Release-Fast",
 		"Build-Ubuntu-GCC-x86_64-Release-Mesa",
 		"Build-Ubuntu-GCC-x86_64-Release-NoGPU",
 		"Build-Ubuntu-GCC-x86_64-Release-PDFium",
@@ -126,7 +126,6 @@
 		"Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-Android",
 		"Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-Android",
 		"Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Android",
-		"Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Android",
 		"Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Debug-Android",
 		// "Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Debug-Android_Vulkan",
 		"Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-Android",
@@ -150,6 +149,7 @@
 		"Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Debug-ASAN",
 		"Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Debug-MSAN",
 		"Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release",
+		"Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast",
 		"Perf-Ubuntu-Clang-Golo-GPU-GT610-x86_64-Debug-ASAN",
 		"Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug",
 		"Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug",
@@ -161,26 +161,38 @@
 		"Perf-Ubuntu-GCC-ShuttleA-GPU-GTX660-x86_64-Debug",
 		"Perf-Ubuntu-GCC-ShuttleA-GPU-GTX660-x86_64-Release",
 		"Perf-Win10-MSVC-Golo-GPU-GT610-x86_64-Release",
+		"Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug",
+		"Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE",
+		"Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan",
+		"Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release",
+		"Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE",
+		"Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan",
 		"Perf-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Debug-ANGLE",
 		"Perf-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Release-ANGLE",
+		"Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug",
+		"Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release",
 		"Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug",
 		"Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan",
 		"Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Release",
 		"Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Release-Vulkan",
+		"Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug",
+		"Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release",
 		"Perf-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug",
 		"Perf-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug-ANGLE",
 		"Perf-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Release",
 		"Perf-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Release-ANGLE",
 		"Perf-Win10-MSVC-ShuttleC-GPU-iHD530-x86_64-Debug",
 		"Perf-Win10-MSVC-ShuttleC-GPU-iHD530-x86_64-Release",
+		"Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug",
+		"Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE",
+		"Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan",
+		"Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release",
+		"Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE",
+		"Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan",
 		"Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86-Debug",
 		"Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Debug",
 		"Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Debug-GDI",
 		"Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Release",
-		"Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug",
-		"Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release",
-		"Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug",
-		"Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release",
 		"Perf-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Debug",
 		"Perf-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Release",
 		"Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Debug-Android",
@@ -229,6 +241,7 @@
 		"Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Debug-ASAN",
 		"Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Debug-MSAN",
 		"Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release",
+		"Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast",
 		"Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-TSAN",
 		"Test-Ubuntu-Clang-Golo-GPU-GT610-x86_64-Debug-ASAN",
 		"Test-Ubuntu-Clang-Golo-GPU-GT610-x86_64-Release-TSAN",
@@ -239,33 +252,44 @@
 		"Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-CT_IMG_DECODE_100k_SKPs",
 		"Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE",
 		"Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release",
-		"Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast",
 		"Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD",
 		"Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind",
 		"Test-Ubuntu-GCC-ShuttleA-GPU-GTX660-x86_64-Debug",
 		"Test-Ubuntu-GCC-ShuttleA-GPU-GTX660-x86_64-Release",
 		"Test-Win10-MSVC-Golo-GPU-GT610-x86_64-Release",
+		"Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug",
+		"Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE",
+		"Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan",
+		"Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release",
+		"Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE",
+		"Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan",
 		"Test-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Debug-ANGLE",
 		"Test-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Release-ANGLE",
+		"Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug",
+		"Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release",
 		"Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug",
 		"Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan",
 		"Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Release",
+		"Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug",
+		"Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release",
 		"Test-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug",
 		"Test-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug-ANGLE",
 		"Test-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Release",
 		"Test-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Release-ANGLE",
 		"Test-Win10-MSVC-ShuttleC-GPU-iHD530-x86_64-Debug",
 		"Test-Win10-MSVC-ShuttleC-GPU-iHD530-x86_64-Release",
+		"Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug",
+		"Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE",
+		"Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan",
+		"Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release",
+		"Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE",
+		"Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan",
 		"Test-Win2k8-MSVC-GCE-CPU-AVX2-x86-Debug",
 		"Test-Win2k8-MSVC-GCE-CPU-AVX2-x86-Release",
 		"Test-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Debug",
 		"Test-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Debug-GDI",
 		"Test-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Release",
 		"Test-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Release-GDI",
-		"Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug",
-		"Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release",
-		"Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug",
-		"Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release",
 		"Test-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Debug",
 		"Test-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Release",
 	}
@@ -334,11 +358,15 @@
 			"Mac":     "Mac-10.11",
 			"Ubuntu":  DEFAULT_OS_LINUX,
 			"Win":     "Windows-2008ServerR2-SP1",
-			"Win10":   "Windows-10-10586",
+			"Win10":   "Windows-10-14393",
 			"Win2k8":  "Windows-2008ServerR2-SP1",
 			"Win8":    "Windows-8.1-SP0",
 			"iOS":     "iOS-9.3.1",
 		}[os]
+		// Chrome Golo has a different Windows image.
+		if parts["model"] == "Golo" && os == "Win10" {
+			d["os"] = "Windows-10-10586"
+		}
 	} else {
 		d["os"] = DEFAULT_OS
 	}
@@ -352,15 +380,15 @@
 				"GalaxyTab3":    {"goyawifi", "JDQ39"},
 				"NVIDIA_Shield": {"foster", "MRA58K"},
 				"Nexus10":       {"manta", "LMY49J"},
-				"Nexus5":        {"hammerhead", "MOB31E"},
+				"Nexus5":        {"hammerhead", "M4B30Z"},
 				"Nexus6":        {"shamu", "M"},
 				"Nexus6p":       {"angler", "NMF26C"},
 				"Nexus7":        {"grouper", "LMY47V"},
 				"Nexus7v2":      {"flo", "M"},
 				"NexusPlayer":   {"fugu", "NRD90R"},
-				"Pixel":         {"sailfish", "NMF25"},
+				"Pixel":         {"sailfish", "NMF26Q"},
 				"PixelC":        {"dragon", "NMF26H"},
-				"PixelXL":       {"marlin", "NMF25"},
+				"PixelXL":       {"marlin", "NMF26Q"},
 			}[parts["model"]]
 			d["device_type"] = deviceInfo[0]
 			d["device_os"] = deviceInfo[1]
@@ -384,15 +412,21 @@
 			}
 		} else {
 			d["gpu"] = map[string]string{
-				"GeForce320M":   "10de:08a4",
-				"GT610":         "10de:104a",
-				"GTX550Ti":      "10de:1244",
-				"GTX660":        "10de:11c0",
-				"GTX960":        "10de:1401",
-				"HD4000":        "8086:0a2e",
-				"HD4600":        "8086:0412",
-				"HD7770":        "1002:683d",
+				"AMDHD7770":   "1002:683d",
+				"GeForce320M": "10de:08a4",
+				"GT610":       "10de:104a",
+				"GTX1070":     "10de:1ba1",
+				"GTX550Ti":    "10de:1244",
+				"GTX660":      "10de:11c0",
+				"GTX960":      "10de:1401",
+				// TODO(benjaminwagner): This device ID doesn't
+				// match HD4000.
+				"HD4000": "8086:0a2e",
+				// This bot will eventually go away, so I'm not
+				// going to bother renaming it.
 				"iHD530":        "8086:1912",
+				"IntelHD4600":   "8086:0412",
+				"IntelIris540":  "8086:1926",
 				"IntelIris6100": "8086:162b",
 			}[parts["cpu_or_gpu_value"]]
 		}
diff --git a/infra/bots/recipe_modules/compile/__init__.py b/infra/bots/recipe_modules/compile/__init__.py
new file mode 100644
index 0000000..219eff5
--- /dev/null
+++ b/infra/bots/recipe_modules/compile/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+  'core',
+  'recipe_engine/json',
+  'recipe_engine/path',
+  'recipe_engine/platform',
+  'recipe_engine/properties',
+  'recipe_engine/python',
+  'flavor',
+  'run',
+  'vars',
+]
diff --git a/infra/bots/recipe_modules/compile/api.py b/infra/bots/recipe_modules/compile/api.py
new file mode 100644
index 0000000..5f332c5
--- /dev/null
+++ b/infra/bots/recipe_modules/compile/api.py
@@ -0,0 +1,81 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Recipe module for Skia Swarming compile.
+
+
+from recipe_engine import recipe_api
+
+
+def build_targets_from_builder_dict(builder_dict):
+  """Return a list of targets to build, depending on the builder type."""
+  if builder_dict.get('extra_config') == 'iOS':
+    return ['iOSShell']
+  return ['most']
+
+
+def get_extra_env_vars(builder_dict):
+  env = {}
+  if builder_dict.get('compiler') == 'Clang':
+    env['CC'] = '/usr/bin/clang'
+    env['CXX'] = '/usr/bin/clang++'
+
+  # SKNX_NO_SIMD, SK_USE_DISCARDABLE_SCALEDIMAGECACHE, etc.
+  extra_config = builder_dict.get('extra_config', '')
+  if extra_config.startswith('SK') and extra_config.isupper():
+    env['CPPFLAGS'] = '-D' + extra_config
+
+  return env
+
+
+def get_gyp_defines(builder_dict):
+  gyp_defs = {}
+
+  if (builder_dict.get('os') == 'iOS' or
+      builder_dict.get('extra_config') == 'iOS'):
+    gyp_defs['skia_arch_type']  = 'arm'
+    gyp_defs['skia_clang_build'] = '1'
+    gyp_defs['skia_os'] = 'ios'
+    gyp_defs['skia_warnings_as_errors'] = 1
+
+  return gyp_defs
+
+
+class CompileApi(recipe_api.RecipeApi):
+  def run(self):
+    self.m.core.setup()
+
+    env = get_extra_env_vars(self.m.vars.builder_cfg)
+    gyp_defs = get_gyp_defines(self.m.vars.builder_cfg)
+    gyp_defs_list = ['%s=%s' % (k, v) for k, v in gyp_defs.iteritems()]
+    gyp_defs_list.sort()
+    env['GYP_DEFINES'] = ' '.join(gyp_defs_list)
+
+    build_targets = build_targets_from_builder_dict(self.m.vars.builder_cfg)
+
+    try:
+      for target in build_targets:
+        self.m.flavor.compile(target, env=env)
+      self.m.run.copy_build_products(
+          self.m.flavor.out_dir,
+          self.m.vars.swarming_out_dir.join(
+              'out', self.m.vars.configuration))
+      self.m.flavor.copy_extra_build_products(self.m.vars.swarming_out_dir)
+    finally:
+      if 'Win' in self.m.vars.builder_cfg.get('os', ''):
+        self.m.python.inline(
+            name='cleanup',
+            program='''import psutil
+for p in psutil.process_iter():
+  try:
+    if p.name in ('mspdbsrv.exe', 'vctip.exe', 'cl.exe', 'link.exe'):
+      p.kill()
+  except psutil._error.AccessDenied:
+    pass
+''',
+            infra_step=True)
+
+    self.m.flavor.cleanup_steps()
+    self.m.run.check_failure()
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-Arm7-Release-iOS.json b/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-Arm7-Release-iOS.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-Arm7-Release-iOS.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-Arm7-Release-iOS.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-arm64-Debug-GN_iOS.json b/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-arm64-Debug-GN_iOS.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-arm64-Debug-GN_iOS.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-arm64-Debug-GN_iOS.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-mipsel-Debug-GN_Android.json b/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-mipsel-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-mipsel-Debug-GN_Android.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-mipsel-Debug-GN_Android.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json b/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-x86_64-Release-GN.json b/infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x86_64-Release-GN.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-x86_64-Release-GN.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Mac-Clang-x86_64-Release-GN.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-x86_64-Debug-ASAN.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Debug-ASAN.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-x86_64-Debug-ASAN.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Debug-ASAN.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-x86_64-Debug-GN.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Debug-GN.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-Clang-x86_64-Debug-GN.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-Clang-x86_64-Debug-GN.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86-Debug.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86-Debug.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86-Debug.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86-Debug.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Debug-GN.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-GN.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Debug-GN.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-GN.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Debug-MSAN.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-MSAN.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Debug-MSAN.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-MSAN.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Debug-NoGPU.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-NoGPU.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Debug-NoGPU.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-NoGPU.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-ANGLE.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-ANGLE.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-ANGLE.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-ANGLE.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-Fast.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Fast.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-Fast.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Fast.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-Mesa.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Mesa.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-Mesa.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Mesa.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-Valgrind.json b/infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Valgrind.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Ubuntu-GCC-x86_64-Release-Valgrind.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Ubuntu-GCC-x86_64-Release-Valgrind.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Win-Clang-arm64-Release-GN_Android.json b/infra/bots/recipe_modules/compile/example.expected/Build-Win-Clang-arm64-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_compile.expected/Build-Win-Clang-arm64-Release-GN_Android.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Win-Clang-arm64-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug-ANGLE.json b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-ANGLE.json
similarity index 98%
rename from infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug-ANGLE.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-ANGLE.json
index 155d46b..a8d496f 100644
--- a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug-ANGLE.json
+++ b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-ANGLE.json
@@ -92,7 +92,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Debug-ANGLE\\Debug",
-      "--args=skia_use_angle=true target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=skia_use_angle=true target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug-Exceptions.json b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-Exceptions.json
similarity index 98%
rename from infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug-Exceptions.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-Exceptions.json
index 83c5f09..d86b258 100644
--- a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug-Exceptions.json
+++ b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug-Exceptions.json
@@ -92,7 +92,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Debug-Exceptions\\Debug",
-      "--args=extra_cflags=[\"/EHsc\"] target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=extra_cflags=[\"/EHsc\"] target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug.json b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug.json
similarity index 98%
rename from infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug.json
index 454ad95..9af4feb 100644
--- a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Debug.json
+++ b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Debug.json
@@ -92,7 +92,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Debug\\Debug",
-      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Release-GDI.json b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GDI.json
similarity index 99%
rename from infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Release-GDI.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GDI.json
index e17720f..8e2519c 100644
--- a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Release-GDI.json
+++ b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GDI.json
@@ -92,7 +92,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Release-GDI\\Release",
-      "--args=is_debug=false skia_use_gdi=true target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=is_debug=false skia_use_gdi=true target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Release-GN.json b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GN.json
similarity index 98%
rename from infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Release-GN.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GN.json
index ea185df..89eef19 100644
--- a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86-Release-GN.json
+++ b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86-Release-GN.json
@@ -92,7 +92,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Release-GN\\Release",
-      "--args=is_debug=false target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=is_debug=false target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json
similarity index 99%
rename from infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json
rename to infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json
index fb8fc4b..7d336f0 100644
--- a/infra/bots/recipes/swarm_compile.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json
+++ b/infra/bots/recipe_modules/compile/example.expected/Build-Win-MSVC-x86_64-Release-Vulkan.json
@@ -92,7 +92,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86_64-Release-Vulkan\\Release_x64",
-      "--args=is_debug=false skia_vulkan_sdk=\"[START_DIR]\\win_vulkan_sdk\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=is_debug=false skia_vulkan_sdk=\"[START_DIR]\\win_vulkan_sdk\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/big_issue_number.json b/infra/bots/recipe_modules/compile/example.expected/big_issue_number.json
similarity index 98%
rename from infra/bots/recipes/swarm_compile.expected/big_issue_number.json
rename to infra/bots/recipe_modules/compile/example.expected/big_issue_number.json
index 5ae2687..f6b1c97 100644
--- a/infra/bots/recipes/swarm_compile.expected/big_issue_number.json
+++ b/infra/bots/recipe_modules/compile/example.expected/big_issue_number.json
@@ -98,7 +98,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Debug\\Debug",
-      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/buildbotless_trybot_gerrit.json b/infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_gerrit.json
similarity index 98%
rename from infra/bots/recipes/swarm_compile.expected/buildbotless_trybot_gerrit.json
rename to infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_gerrit.json
index a25dbc2..e1d93a1 100644
--- a/infra/bots/recipes/swarm_compile.expected/buildbotless_trybot_gerrit.json
+++ b/infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_gerrit.json
@@ -118,7 +118,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Debug\\Debug",
-      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/buildbotless_trybot_rietveld.json b/infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_rietveld.json
similarity index 98%
rename from infra/bots/recipes/swarm_compile.expected/buildbotless_trybot_rietveld.json
rename to infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_rietveld.json
index 212497a..b4c1c07 100644
--- a/infra/bots/recipes/swarm_compile.expected/buildbotless_trybot_rietveld.json
+++ b/infra/bots/recipe_modules/compile/example.expected/buildbotless_trybot_rietveld.json
@@ -120,7 +120,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Debug\\Debug",
-      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipes/swarm_compile.expected/recipe_with_gerrit_patch.json b/infra/bots/recipe_modules/compile/example.expected/recipe_with_gerrit_patch.json
similarity index 98%
rename from infra/bots/recipes/swarm_compile.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipe_modules/compile/example.expected/recipe_with_gerrit_patch.json
index 896d8f4..880e002 100644
--- a/infra/bots/recipes/swarm_compile.expected/recipe_with_gerrit_patch.json
+++ b/infra/bots/recipe_modules/compile/example.expected/recipe_with_gerrit_patch.json
@@ -118,7 +118,7 @@
       "gn.bat",
       "gen",
       "[CUSTOM_C:\\_B_WORK]\\skia\\out\\Build-Win-MSVC-x86-Debug-Trybot\\Debug",
-      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\95ddda401ec5678f15eeed01d2bee08fcbc5ee97\""
+      "--args=target_cpu=\"x86\" windk=\"[START_DIR]\\t\\depot_tools\\win_toolchain\\vs_files\\d3cb0e37bdd120ad0ac4650b674b09e81be45616\""
     ],
     "cwd": "[CUSTOM_C:\\_B_WORK]\\skia",
     "env": {
diff --git a/infra/bots/recipe_modules/compile/example.py b/infra/bots/recipe_modules/compile/example.py
new file mode 100644
index 0000000..6cd948f
--- /dev/null
+++ b/infra/bots/recipe_modules/compile/example.py
@@ -0,0 +1,168 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Example recipe w/ coverage.
+
+
+DEPS = [
+  'compile',
+  'recipe_engine/path',
+  'recipe_engine/platform',
+  'recipe_engine/properties',
+]
+
+
+TEST_BUILDERS = {
+  'client.skia.compile': {
+    'skiabot-linux-swarm-000': [
+      'Build-Mac-Clang-Arm7-Release-iOS',
+      'Build-Mac-Clang-arm64-Debug-GN_iOS',
+      'Build-Mac-Clang-mipsel-Debug-GN_Android',
+      'Build-Mac-Clang-x86_64-Debug-CommandBuffer',
+      'Build-Mac-Clang-x86_64-Release-GN',
+      'Build-Ubuntu-Clang-arm64-Release-GN_Android',
+      'Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan',
+      'Build-Ubuntu-Clang-x86_64-Debug-ASAN',
+      'Build-Ubuntu-Clang-x86_64-Debug-GN',
+      'Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot',
+      'Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs',
+      'Build-Ubuntu-GCC-x86-Debug',
+      'Build-Ubuntu-GCC-x86_64-Debug-GN',
+      'Build-Ubuntu-GCC-x86_64-Debug-MSAN',
+      'Build-Ubuntu-GCC-x86_64-Debug-NoGPU',
+      'Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE',
+      'Build-Ubuntu-GCC-x86_64-Release-ANGLE',
+      'Build-Ubuntu-GCC-x86_64-Release-Fast',
+      'Build-Ubuntu-GCC-x86_64-Release-Mesa',
+      'Build-Ubuntu-GCC-x86_64-Release-PDFium',
+      'Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths',
+      'Build-Ubuntu-GCC-x86_64-Release-Valgrind',
+      'Build-Win-Clang-arm64-Release-GN_Android',
+      'Build-Win-MSVC-x86-Debug',
+      'Build-Win-MSVC-x86-Debug-ANGLE',
+      'Build-Win-MSVC-x86-Debug-Exceptions',
+      'Build-Win-MSVC-x86-Release-GDI',
+      'Build-Win-MSVC-x86-Release-GN',
+      'Build-Win-MSVC-x86_64-Release-Vulkan',
+    ],
+  },
+}
+
+
+def RunSteps(api):
+  api.compile.run()
+
+
+def GenTests(api):
+  for mastername, slaves in TEST_BUILDERS.iteritems():
+    for slavename, builders_by_slave in slaves.iteritems():
+      for builder in builders_by_slave:
+        test = (
+          api.test(builder) +
+          api.properties(buildername=builder,
+                         mastername=mastername,
+                         slavename=slavename,
+                         buildnumber=5,
+                         revision='abc123',
+                         path_config='kitchen',
+                         swarm_out_dir='[SWARM_OUT_DIR]') +
+          api.path.exists(
+              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+          )
+        )
+        if 'Win' in builder:
+          test += api.platform('win', 64)
+        elif 'Mac' in builder:
+          test += api.platform('mac', 64)
+        else:
+          test += api.platform('linux', 64)
+        if 'Trybot' in builder:
+          test += api.properties(issue=500,
+                                 patchset=1,
+                                 rietveld='https://codereview.chromium.org')
+
+        yield test
+
+  mastername = 'client.skia.compile'
+  slavename = 'skiabot-win-compile-000'
+  buildername = 'Build-Win-MSVC-x86-Debug'
+  yield (
+      api.test('big_issue_number') +
+      api.properties(buildername=buildername,
+                     mastername=mastername,
+                     slavename=slavename,
+                     buildnumber=5,
+                     revision='abc123',
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]',
+                     rietveld='https://codereview.chromium.org',
+                     patchset=1,
+                     issue=2147533002L) +
+      api.path.exists(
+          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+      ) +
+      api.platform('win', 64)
+  )
+
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=buildername + '-Trybot',
+          mastername=mastername,
+          slavename=slavename,
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=buildername + '-Trybot',
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.platform('win', 64)
+  )
+
+  yield (
+      api.test('buildbotless_trybot_rietveld') +
+      api.properties(
+          repository='skia',
+          buildername=buildername,
+          mastername=mastername,
+          slavename=slavename,
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          nobuildbot='True',
+          issue=500,
+          patchset=1,
+          patch_storage='rietveld',
+          rietveld='https://codereview.chromium.org') +
+      api.platform('win', 64)
+  )
+
+  yield (
+      api.test('buildbotless_trybot_gerrit') +
+      api.properties(
+          repository='skia',
+          buildername=buildername,
+          mastername=mastername,
+          slavename=slavename,
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          nobuildbot='True',
+          patch_issue=500,
+          patch_set=1,
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=buildername,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.platform('win', 64)
+  )
diff --git a/infra/bots/recipe_modules/flavor/gn_flavor.py b/infra/bots/recipe_modules/flavor/gn_flavor.py
index af400d9..8d0fa4c 100644
--- a/infra/bots/recipe_modules/flavor/gn_flavor.py
+++ b/infra/bots/recipe_modules/flavor/gn_flavor.py
@@ -40,7 +40,7 @@
     clang_linux   = str(self.m.vars.slave_dir.join('clang_linux'))
     win_toolchain = str(self.m.vars.slave_dir.join(
       't', 'depot_tools', 'win_toolchain', 'vs_files',
-      '95ddda401ec5678f15eeed01d2bee08fcbc5ee97'))
+      'd3cb0e37bdd120ad0ac4650b674b09e81be45616'))
     win_vulkan_sdk = str(self.m.vars.slave_dir.join('win_vulkan_sdk'))
 
     cc, cxx = None, None
diff --git a/infra/bots/recipe_modules/perf/__init__.py b/infra/bots/recipe_modules/perf/__init__.py
new file mode 100644
index 0000000..10649df
--- /dev/null
+++ b/infra/bots/recipe_modules/perf/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+  'build/file',
+  'core',
+  'recipe_engine/json',
+  'recipe_engine/path',
+  'recipe_engine/platform',
+  'recipe_engine/properties',
+  'recipe_engine/raw_io',
+  'recipe_engine/time',
+  'run',
+  'flavor',
+  'vars',
+]
diff --git a/infra/bots/recipe_modules/perf/api.py b/infra/bots/recipe_modules/perf/api.py
new file mode 100644
index 0000000..52ea32d
--- /dev/null
+++ b/infra/bots/recipe_modules/perf/api.py
@@ -0,0 +1,217 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Recipe module for Skia Swarming perf.
+
+
+import calendar
+
+from recipe_engine import recipe_api
+
+
+def nanobench_flags(bot):
+  args = ['--pre_log']
+
+  if 'GPU' in bot:
+    args.append('--images')
+    args.extend(['--gpuStatsDump', 'true'])
+
+  if 'Android' in bot and 'GPU' in bot:
+    args.extend(['--useThermalManager', '1,1,10,1000'])
+
+  args.extend(['--scales', '1.0', '1.1'])
+
+  if 'iOS' in bot:
+    args.extend(['--skps', 'ignore_skps'])
+
+  config = ['8888', 'gpu', 'nonrendering', 'hwui' ]
+  if 'AndroidOne' not in bot:
+    config += [ 'f16', 'srgb' ]
+  if '-GCE-' in bot:
+    config += [ '565' ]
+  # The NP produces a long error stream when we run with MSAA.
+  if 'NexusPlayer' not in bot:
+    if 'Android' in bot:
+      # The NVIDIA_Shield has a regular OpenGL implementation. We bench that
+      # instead of ES.
+      if 'NVIDIA_Shield' in bot:
+        config.remove('gpu')
+        config.extend(['gl', 'glmsaa4', 'glnvpr4', 'glnvprdit4'])
+      else:
+        config.extend(['msaa4', 'nvpr4', 'nvprdit4'])
+    else:
+      config.extend(['msaa16', 'nvpr16', 'nvprdit16'])
+
+  # Bench instanced rendering on a limited number of platforms
+  if 'Nexus6' in bot:
+    config.append('esinst') # esinst4 isn't working yet on Adreno.
+  elif 'PixelC' in bot:
+    config.extend(['esinst', 'esinst4'])
+  elif 'NVIDIA_Shield' in bot:
+    config.extend(['glinst', 'glinst4'])
+  elif 'MacMini6.2' in bot:
+    config.extend(['glinst', 'glinst16'])
+
+  if 'CommandBuffer' in bot:
+    config = ['commandbuffer']
+  if 'Vulkan' in bot:
+    config = ['vk']
+
+  if 'ANGLE' in bot:
+    config.extend(['angle_d3d11_es2'])
+    # The GL backend of ANGLE crashes on the perf bot currently.
+    if 'Win' not in bot:
+      config.extend(['angle_gl_es2'])
+
+  args.append('--config')
+  args.extend(config)
+
+  if 'Valgrind' in bot:
+    # Don't care about Valgrind performance.
+    args.extend(['--loops',   '1'])
+    args.extend(['--samples', '1'])
+    # Ensure that the bot framework does not think we have timed out.
+    args.extend(['--keepAlive', 'true'])
+
+  match = []
+  if 'Android' in bot:
+    # Segfaults when run as GPU bench. Very large texture?
+    match.append('~blurroundrect')
+    match.append('~patch_grid')  # skia:2847
+    match.append('~desk_carsvg')
+  if 'NexusPlayer' in bot:
+    match.append('~desk_unicodetable')
+  if 'Nexus5' in bot:
+    match.append('~keymobi_shop_mobileweb_ebay_com.skp')  # skia:5178
+  if 'iOS' in bot:
+    match.append('~blurroundrect')
+    match.append('~patch_grid')  # skia:2847
+    match.append('~desk_carsvg')
+    match.append('~keymobi')
+    match.append('~path_hairline')
+    match.append('~GLInstancedArraysBench') # skia:4714
+  if 'IntelIris540' in bot and 'ANGLE' in bot:
+    match.append('~tile_image_filter_tiled_64')  # skia:6082
+
+  # We do not need or want to benchmark the decodes of incomplete images.
+  # In fact, in nanobench we assert that the full image decode succeeds.
+  match.append('~inc0.gif')
+  match.append('~inc1.gif')
+  match.append('~incInterlaced.gif')
+  match.append('~inc0.jpg')
+  match.append('~incGray.jpg')
+  match.append('~inc0.wbmp')
+  match.append('~inc1.wbmp')
+  match.append('~inc0.webp')
+  match.append('~inc1.webp')
+  match.append('~inc0.ico')
+  match.append('~inc1.ico')
+  match.append('~inc0.png')
+  match.append('~inc1.png')
+  match.append('~inc2.png')
+  match.append('~inc12.png')
+  match.append('~inc13.png')
+  match.append('~inc14.png')
+  match.append('~inc0.webp')
+  match.append('~inc1.webp')
+
+  if match:
+    args.append('--match')
+    args.extend(match)
+
+  return args
+
+
+def perf_steps(api):
+  """Run Skia benchmarks."""
+  if api.vars.upload_perf_results:
+    api.flavor.create_clean_device_dir(
+        api.flavor.device_dirs.perf_data_dir)
+
+  # Run nanobench.
+  properties = [
+    '--properties',
+    'gitHash',      api.vars.got_revision,
+    'build_number', api.vars.build_number,
+  ]
+  if api.vars.is_trybot:
+    properties.extend([
+      'issue',    api.vars.issue,
+      'patchset', api.vars.patchset,
+      'patch_storage', api.vars.patch_storage,
+    ])
+  if api.vars.no_buildbot:
+    properties.extend(['no_buildbot', 'True'])
+    properties.extend(['swarming_bot_id', api.vars.swarming_bot_id])
+    properties.extend(['swarming_task_id', api.vars.swarming_task_id])
+
+  target = 'nanobench'
+  args = [
+      target,
+      '--undefok',   # This helps branches that may not know new flags.
+      '-i',       api.flavor.device_dirs.resource_dir,
+      '--skps',   api.flavor.device_dirs.skp_dir,
+      '--images', api.flavor.device_path_join(
+          api.flavor.device_dirs.images_dir, 'nanobench'),
+  ]
+
+  # Do not run svgs on Valgrind.
+  if 'Valgrind' not in api.vars.builder_name:
+    args.extend(['--svgs',  api.flavor.device_dirs.svg_dir])
+
+  skip_flag = None
+  if api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
+    skip_flag = '--nogpu'
+  elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
+    skip_flag = '--nocpu'
+  if skip_flag:
+    args.append(skip_flag)
+  args.extend(nanobench_flags(api.vars.builder_name))
+
+  if api.vars.upload_perf_results:
+    now = api.time.utcnow()
+    ts = int(calendar.timegm(now.utctimetuple()))
+    json_path = api.flavor.device_path_join(
+        api.flavor.device_dirs.perf_data_dir,
+        'nanobench_%s_%d.json' % (api.vars.got_revision, ts))
+    args.extend(['--outResultsFile', json_path])
+    args.extend(properties)
+
+    keys_blacklist = ['configuration', 'role', 'is_trybot']
+    args.append('--key')
+    for k in sorted(api.vars.builder_cfg.keys()):
+      if not k in keys_blacklist:
+        args.extend([k, api.vars.builder_cfg[k]])
+
+  api.run(api.flavor.step, target, cmd=args,
+          abort_on_failure=False,
+          env=api.vars.default_env)
+
+  # See skia:2789.
+  if ('Valgrind' in api.vars.builder_name and
+      api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU'):
+    abandonGpuContext = list(args)
+    abandonGpuContext.extend(['--abandonGpuContext', '--nocpu'])
+    api.run(api.flavor.step,
+            '%s --abandonGpuContext' % target,
+            cmd=abandonGpuContext, abort_on_failure=False,
+            env=api.vars.default_env)
+
+  # Copy results to swarming out dir.
+  if api.vars.upload_perf_results:
+    api.file.makedirs('perf_dir', api.vars.perf_data_dir)
+    api.flavor.copy_directory_contents_to_host(
+        api.flavor.device_dirs.perf_data_dir,
+        api.vars.perf_data_dir)
+
+class PerfApi(recipe_api.RecipeApi):
+  def run(self):
+    self.m.core.setup()
+    try:
+      self.m.flavor.install_everything()
+      perf_steps(self.m)
+    finally:
+      self.m.flavor.cleanup_steps()
+    self.m.run.check_failure()
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android_Vulkan.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android_Vulkan.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android_Vulkan.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android_Vulkan.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release.json
diff --git a/infra/bots/recipe_modules/perf/example.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE.json
new file mode 100644
index 0000000..4db8b0b
--- /dev/null
+++ b/infra/bots/recipe_modules/perf/example.expected/Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE.json
@@ -0,0 +1,211 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skp\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SKP VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SKP_VERSION"
+    ],
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skimage\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SK_IMAGE_VERSION"
+    ],
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\svg\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SVG VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SVG_VERSION"
+    ],
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os, sys\nfrom common import chromium_utils # Error? See https://crbug.com/584783.\n\n\nif os.path.exists(sys.argv[1]):\n  chromium_utils.RemoveDirectory(sys.argv[1])\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\perfdata\\Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE\\data"
+    ],
+    "env": {
+      "PYTHONPATH": "[START_DIR]\\skia\\infra\\bots\\.recipe_deps\\build\\scripts"
+    },
+    "name": "rmtree data",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os, sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@from common import chromium_utils # Error? See https://crbug.com/584783.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@if os.path.exists(sys.argv[1]):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  chromium_utils.RemoveDirectory(sys.argv[1])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\perfdata\\Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE\\data",
+      "511"
+    ],
+    "name": "makedirs data",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\out\\Release_x64\\nanobench",
+      "--undefok",
+      "-i",
+      "[START_DIR]\\skia\\resources",
+      "--skps",
+      "[START_DIR]\\skp",
+      "--images",
+      "[START_DIR]\\skimage\\nanobench",
+      "--svgs",
+      "[START_DIR]\\svg",
+      "--nocpu",
+      "--pre_log",
+      "--images",
+      "--gpuStatsDump",
+      "true",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--config",
+      "8888",
+      "gpu",
+      "nonrendering",
+      "hwui",
+      "f16",
+      "srgb",
+      "msaa16",
+      "nvpr16",
+      "nvprdit16",
+      "angle_d3d11_es2",
+      "--match",
+      "~tile_image_filter_tiled_64",
+      "~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",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\perfdata\\Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE\\data\\nanobench_abc123_1337000001.json",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "build_number",
+      "5",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "MSVC",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelIris540",
+      "extra_config",
+      "ANGLE",
+      "model",
+      "NUC",
+      "os",
+      "Win10"
+    ],
+    "cwd": "[START_DIR]\\skia",
+    "name": "nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\perfdata\\Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE\\data",
+      "511"
+    ],
+    "name": "makedirs perf_dir",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot.json b/infra/bots/recipe_modules/perf/example.expected/Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot.json
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug.json b/infra/bots/recipe_modules/perf/example.expected/Perf-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/Perf-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug.json
rename to infra/bots/recipe_modules/perf/example.expected/Perf-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug.json
diff --git a/infra/bots/recipes/swarm_perf.expected/big_issue_number.json b/infra/bots/recipe_modules/perf/example.expected/big_issue_number.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/big_issue_number.json
rename to infra/bots/recipe_modules/perf/example.expected/big_issue_number.json
diff --git a/infra/bots/recipes/swarm_perf.expected/nobuildbot.json b/infra/bots/recipe_modules/perf/example.expected/nobuildbot.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/nobuildbot.json
rename to infra/bots/recipe_modules/perf/example.expected/nobuildbot.json
diff --git a/infra/bots/recipes/swarm_perf.expected/recipe_with_gerrit_patch.json b/infra/bots/recipe_modules/perf/example.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipes/swarm_perf.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipe_modules/perf/example.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipe_modules/perf/example.py b/infra/bots/recipe_modules/perf/example.py
new file mode 100644
index 0000000..16cde3b
--- /dev/null
+++ b/infra/bots/recipe_modules/perf/example.py
@@ -0,0 +1,157 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Example recipe w/ coverage.
+
+
+DEPS = [
+  'perf',
+  'recipe_engine/path',
+  'recipe_engine/platform',
+  'recipe_engine/properties',
+  'recipe_engine/raw_io',
+]
+
+
+TEST_BUILDERS = {
+  'client.skia': {
+    'skiabot-linux-swarm-000': [
+      ('Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug' +
+       '-GN_Android_Vulkan'),
+      'Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android',
+      'Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android',
+      'Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android',
+      'Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android',
+      'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android',
+      'Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN',
+      'Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer',
+      'Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN',
+      'Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind',
+      'Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE',
+      'Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug',
+      'Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release',
+      'Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE',
+      'Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE',
+      'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot',
+      'Perf-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug',
+    ],
+  },
+}
+
+
+def RunSteps(api):
+  api.perf.run()
+
+
+def GenTests(api):
+  for mastername, slaves in TEST_BUILDERS.iteritems():
+    for slavename, builders_by_slave in slaves.iteritems():
+      for builder in builders_by_slave:
+        test = (
+          api.test(builder) +
+          api.properties(buildername=builder,
+                         mastername=mastername,
+                         slavename=slavename,
+                         buildnumber=5,
+                         revision='abc123',
+                         path_config='kitchen',
+                         swarm_out_dir='[SWARM_OUT_DIR]') +
+          api.path.exists(
+              api.path['start_dir'].join('skia'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skimage', 'VERSION'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skp', 'VERSION'),
+              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+          )
+        )
+        if 'Trybot' in builder:
+          test += api.properties(issue=500,
+                                 patchset=1,
+                                 rietveld='https://codereview.chromium.org')
+        if 'Win' in builder:
+          test += api.platform('win', 64)
+
+        yield test
+
+  builder = 'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot'
+  yield (
+    api.test('big_issue_number') +
+    api.properties(buildername=builder,
+                   mastername='client.skia.compile',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=5,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]',
+                   rietveld='https://codereview.chromium.org',
+                   patchset=1,
+                   issue=2147533002L) +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.platform('win', 64)
+  )
+
+  builder = ('Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind-'
+             'Trybot')
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=builder,
+          mastername='client.skia',
+          slavename='skiabot-linux-swarm-000',
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      )
+  )
+
+  builder = 'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot'
+  yield (
+      api.test('nobuildbot') +
+      api.properties(
+          buildername=builder,
+          mastername='client.skia',
+          slavename='skiabot-linux-swarm-000',
+          buildnumber=5,
+          revision='abc123',
+          path_config='kitchen',
+          nobuildbot='True',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.path.exists(
+          api.path['start_dir'].join('skia'),
+          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                       'skimage', 'VERSION'),
+          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                       'skp', 'VERSION'),
+          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                       'svg', 'VERSION'),
+          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+      ) +
+      api.platform('win', 64) +
+      api.step_data('get swarming bot id',
+          stdout=api.raw_io.output('skia-bot-123')) +
+      api.step_data('get swarming task id', stdout=api.raw_io.output('123456'))
+  )
diff --git a/infra/bots/recipe_modules/skpbench/__init__.py b/infra/bots/recipe_modules/skpbench/__init__.py
new file mode 100644
index 0000000..f2a0bfe
--- /dev/null
+++ b/infra/bots/recipe_modules/skpbench/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+  'build/file',
+  'core',
+  'recipe_engine/path',
+  'recipe_engine/properties',
+  'recipe_engine/python',
+  'recipe_engine/raw_io',
+  'recipe_engine/step',
+  'recipe_engine/time',
+  'run',
+  'flavor',
+  'vars',
+]
diff --git a/infra/bots/recipe_modules/skpbench/api.py b/infra/bots/recipe_modules/skpbench/api.py
new file mode 100644
index 0000000..85efd75
--- /dev/null
+++ b/infra/bots/recipe_modules/skpbench/api.py
@@ -0,0 +1,89 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Recipe module for Skia Swarming skpbench.
+
+
+import calendar
+
+from recipe_engine import recipe_api
+
+
+def _run(api, title, *cmd, **kwargs):
+  return api.run(api.step, title, cmd=list(cmd),
+                 cwd=api.vars.skia_dir, **kwargs)
+
+
+def _adb(api, title, *cmd, **kwargs):
+  if 'infra_step' not in kwargs:
+    kwargs['infra_step'] = True
+  return _run(api, title, 'adb', *cmd, **kwargs)
+
+
+def skpbench_steps(api):
+  """benchmark Skia using skpbench."""
+  app = api.vars.skia_out.join(api.vars.configuration, 'skpbench')
+  _adb(api, 'push skpbench', 'push', app, api.vars.android_bin_dir)
+
+  skpbench_dir = api.vars.slave_dir.join('skia', 'tools', 'skpbench')
+  table = api.path.join(api.vars.swarming_out_dir, 'table')
+
+  config = 'gpu,esinst4'
+  if 'Vulkan' in api.vars.builder_name:
+    config = 'vk'
+
+  skpbench_args = [
+        api.path.join(api.vars.android_bin_dir, 'skpbench'),
+        api.path.join(api.vars.android_data_dir, 'skps'),
+        '--adb',
+        '--resultsfile', table,
+        '--config', config]
+
+  api.run(api.python, 'skpbench',
+      script=skpbench_dir.join('skpbench.py'),
+      args=skpbench_args)
+
+  skiaperf_args = [
+    table,
+    '--properties',
+    'gitHash',      api.vars.got_revision,
+    'build_number', api.vars.build_number,
+  ]
+
+  skiaperf_args.extend(['no_buildbot', 'True'])
+  skiaperf_args.extend(['swarming_bot_id', api.vars.swarming_bot_id])
+  skiaperf_args.extend(['swarming_task_id', api.vars.swarming_task_id])
+
+  now = api.time.utcnow()
+  ts = int(calendar.timegm(now.utctimetuple()))
+  api.file.makedirs('perf_dir', api.vars.perf_data_dir)
+  json_path = api.path.join(
+      api.vars.perf_data_dir,
+      'skpbench_%s_%d.json' % (api.vars.got_revision, ts))
+
+  skiaperf_args.extend([
+    '--outfile', json_path
+  ])
+
+  keys_blacklist = ['configuration', 'role', 'is_trybot']
+  skiaperf_args.append('--key')
+  for k in sorted(api.vars.builder_cfg.keys()):
+    if not k in keys_blacklist:
+      skiaperf_args.extend([k, api.vars.builder_cfg[k]])
+
+  api.run(api.python, 'Parse skpbench output into Perf json',
+      script=skpbench_dir.join('skiaperf.py'),
+      args=skiaperf_args)
+
+
+class SkpBenchApi(recipe_api.RecipeApi):
+  def run(self):
+    self.m.core.setup()
+    try:
+      self.m.flavor.install(skps=True)
+      skpbench_steps(self.m)
+    finally:
+      self.m.flavor.cleanup_steps()
+    self.m.run.check_failure()
diff --git a/infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench.json b/infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench.json
new file mode 100644
index 0000000..6353575
--- /dev/null
+++ b/infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench.json
@@ -0,0 +1,290 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[START_DIR]/tmp",
+      "511"
+    ],
+    "name": "makedirs tmp_dir",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/resources"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/resources"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SKP VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "adb",
+      "shell",
+      "cat",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "read /sdcard/revenge_of_the_skiabot/SKP_VERSION",
+    "stdout": "/path/to/tmp/"
+  },
+  {
+    "cmd": [
+      "adb",
+      "shell",
+      "rm",
+      "-f",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "rm /sdcard/revenge_of_the_skiabot/SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "adb",
+      "shell",
+      "rm",
+      "-rf",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "rm /sdcard/revenge_of_the_skiabot/skps"
+  },
+  {
+    "cmd": [
+      "adb",
+      "shell",
+      "mkdir",
+      "-p",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "mkdir /sdcard/revenge_of_the_skiabot/skps"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nhost   = sys.argv[1]\ndevice = sys.argv[2]\nfor d, _, fs in os.walk(host):\n  p = os.path.relpath(d, host)\n  if p != '.' and p.startswith('.'):\n    continue\n  for f in fs:\n    print os.path.join(p,f)\n    subprocess.check_call(['adb', 'push',\n                           os.path.realpath(os.path.join(host, p, f)),\n                           os.path.join(device, p, f)])\n",
+      "[START_DIR]/skp",
+      "/sdcard/revenge_of_the_skiabot/skps"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "push [START_DIR]/skp/* /sdcard/revenge_of_the_skiabot/skps",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@host   = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@device = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@for d, _, fs in os.walk(host):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  p = os.path.relpath(d, host)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if p != '.' and p.startswith('.'):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    continue@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in fs:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print os.path.join(p,f)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    subprocess.check_call(['adb', 'push',@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.realpath(os.path.join(host, p, f)),@@@",
+      "@@@STEP_LOG_LINE@python.inline@                           os.path.join(device, p, f)])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "adb",
+      "push",
+      "[START_DIR]/tmp/SKP_VERSION",
+      "/sdcard/revenge_of_the_skiabot/SKP_VERSION"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "push [START_DIR]/tmp/SKP_VERSION /sdcard/revenge_of_the_skiabot/SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "adb",
+      "push",
+      "[START_DIR]/out/Release/skpbench",
+      "/data/local/tmp/"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "push skpbench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/skia/tools/skpbench/skpbench.py",
+      "/data/local/tmp/skpbench",
+      "/sdcard/revenge_of_the_skiabot/skps",
+      "--adb",
+      "--resultsfile",
+      "[CUSTOM_[SWARM_OUT_DIR]]/table",
+      "--config",
+      "gpu,esinst4"
+    ],
+    "name": "skpbench"
+  },
+  {
+    "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": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench/data",
+      "511"
+    ],
+    "name": "makedirs perf_dir",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/skia/tools/skpbench/skiaperf.py",
+      "[CUSTOM_[SWARM_OUT_DIR]]/table",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "build_number",
+      "5",
+      "no_buildbot",
+      "True",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--outfile",
+      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench/data/skpbench_abc123_1337000001.json",
+      "--key",
+      "arch",
+      "arm64",
+      "compiler",
+      "Clang",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "TegraX1",
+      "extra_config",
+      "GN_Android_Skpbench",
+      "model",
+      "PixelC",
+      "os",
+      "Android"
+    ],
+    "name": "Parse skpbench output into Perf json"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['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",
+      "[START_DIR]/out/Release"
+    ],
+    "name": "dump log",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import subprocess@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@out = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@log = subprocess.check_output(['adb', 'logcat', '-d'])@@@",
+      "@@@STEP_LOG_LINE@python.inline@for line in log.split('\\n'):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  tokens = line.split()@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':@@@",
+      "@@@STEP_LOG_LINE@python.inline@    addr, path = tokens[-2:]@@@",
+      "@@@STEP_LOG_LINE@python.inline@    local = os.path.join(out, os.path.basename(path))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if os.path.exists(local):@@@",
+      "@@@STEP_LOG_LINE@python.inline@      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])@@@",
+      "@@@STEP_LOG_LINE@python.inline@      line = line.replace(addr, addr + ' ' + sym.strip())@@@",
+      "@@@STEP_LOG_LINE@python.inline@  print line@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "adb",
+      "kill-server"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "kill adb server"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/swarm_skpbench.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Vulkan_Skpbench.json b/infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Vulkan_Skpbench.json
similarity index 100%
rename from infra/bots/recipes/swarm_skpbench.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Vulkan_Skpbench.json
rename to infra/bots/recipe_modules/skpbench/example.expected/Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Vulkan_Skpbench.json
diff --git a/infra/bots/recipe_modules/skpbench/example.py b/infra/bots/recipe_modules/skpbench/example.py
new file mode 100644
index 0000000..4acf916
--- /dev/null
+++ b/infra/bots/recipe_modules/skpbench/example.py
@@ -0,0 +1,56 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Example recipe w/ coverage.
+
+
+DEPS = [
+  'recipe_engine/path',
+  'recipe_engine/properties',
+  'recipe_engine/raw_io',
+  'skpbench',
+]
+
+
+TEST_BUILDERS = {
+  'client.skia': {
+    'skiabot-linux-swarm-000': [
+      'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench',
+      ('Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-' +
+      'GN_Android_Vulkan_Skpbench'),
+    ],
+  },
+}
+
+
+def RunSteps(api):
+  api.skpbench.run()
+
+
+def GenTests(api):
+  for mastername, slaves in TEST_BUILDERS.iteritems():
+    for slavename, builders_by_slave in slaves.iteritems():
+      for builder in builders_by_slave:
+        test = (
+          api.test(builder) +
+          api.properties(buildername=builder,
+                         mastername=mastername,
+                         slavename=slavename,
+                         buildnumber=5,
+                         revision='abc123',
+                         path_config='kitchen',
+                         swarm_out_dir='[SWARM_OUT_DIR]') +
+          api.path.exists(
+              api.path['start_dir'].join('skia'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skp', 'VERSION'),
+          ) +
+          api.step_data('get swarming bot id',
+              stdout=api.raw_io.output('skia-bot-123')) +
+          api.step_data('get swarming task id',
+              stdout=api.raw_io.output('123456'))
+        )
+
+        yield test
diff --git a/infra/bots/recipe_modules/sktest/__init__.py b/infra/bots/recipe_modules/sktest/__init__.py
new file mode 100644
index 0000000..578c162
--- /dev/null
+++ b/infra/bots/recipe_modules/sktest/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+  'build/file',
+  'core',
+  'recipe_engine/json',
+  'recipe_engine/path',
+  'recipe_engine/platform',
+  'recipe_engine/properties',
+  'recipe_engine/python',
+  'recipe_engine/raw_io',
+  'flavor',
+  'run',
+  'vars',
+]
diff --git a/infra/bots/recipe_modules/sktest/api.py b/infra/bots/recipe_modules/sktest/api.py
new file mode 100644
index 0000000..2406158
--- /dev/null
+++ b/infra/bots/recipe_modules/sktest/api.py
@@ -0,0 +1,529 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Recipe module for Skia Swarming test.
+
+
+from recipe_engine import recipe_api
+
+
+def dm_flags(bot):
+  args = []
+
+  # 32-bit desktop bots tend to run out of memory, because they have relatively
+  # far more cores than RAM (e.g. 32 cores, 3G RAM).  Hold them back a bit.
+  if '-x86-' in bot and not 'NexusPlayer' in bot:
+    args.extend('--threads 4'.split(' '))
+
+  # These are the canonical configs that we would ideally run on all bots. We
+  # may opt out or substitute some below for specific bots
+  configs = ['8888', 'srgb', 'gpu', 'gpudft', 'gpusrgb', 'pdf']
+  # Add in either msaa4 or msaa16 to the canonical set of configs to run
+  if 'Android' in bot or 'iOS' in bot:
+    configs.append('msaa4')
+  else:
+    configs.append('msaa16')
+
+  # The NP produces a long error stream when we run with MSAA. The Tegra3 just
+  # doesn't support it.
+  if ('NexusPlayer' in bot or
+      'Tegra3'      in bot or
+      # We aren't interested in fixing msaa bugs on iPad4.
+      'iPad4' in bot or
+      # skia:5792
+      'iHD530'       in bot or
+      'IntelIris540' in bot):
+    configs = [x for x in configs if 'msaa' not in x]
+
+  # The NP produces different images for dft on every run.
+  if 'NexusPlayer' in bot:
+    configs = [x for x in configs if 'gpudft' not in x]
+
+  # Runs out of memory on Android bots.  Everyone else seems fine.
+  if 'Android' in bot:
+    configs.remove('pdf')
+
+  if '-GCE-' in bot:
+    configs.extend(['565'])
+    configs.extend(['f16'])
+    configs.extend(['sp-8888', '2ndpic-8888'])   # Test niche uses of SkPicture.
+    configs.extend(['lite-8888'])                # Experimental display list.
+
+  if '-TSAN' not in bot:
+    if ('TegraK1'  in bot or
+        'TegraX1'  in bot or
+        'GTX550Ti' in bot or
+        'GTX660'   in bot or
+        'GT610'    in bot):
+      if 'Android' in bot:
+        configs.append('nvprdit4')
+      else:
+        configs.append('nvprdit16')
+
+  # We want to test the OpenGL config not the GLES config on the Shield
+  if 'NVIDIA_Shield' in bot:
+    configs = [x.replace('gpu', 'gl') for x in configs]
+    configs = [x.replace('msaa', 'glmsaa') for x in configs]
+    configs = [x.replace('nvpr', 'glnvpr') for x in configs]
+
+  # NP is running out of RAM when we run all these modes.  skia:3255
+  if 'NexusPlayer' not in bot:
+    configs.extend(mode + '-8888' for mode in
+                   ['serialize', 'tiles_rt', 'pic'])
+
+  # Test instanced rendering on a limited number of platforms
+  if 'Nexus6' in bot:
+    configs.append('esinst') # esinst4 isn't working yet on Adreno.
+  elif 'NVIDIA_Shield' in bot:
+    # Multisampled instanced configs use nvpr.
+    configs = [x.replace('glnvpr', 'glinst') for x in configs]
+    configs.append('glinst')
+  elif 'PixelC' in bot:
+    # Multisampled instanced configs use nvpr.
+    configs = [x.replace('nvpr', 'esinst') for x in configs]
+    configs.append('esinst')
+  elif 'MacMini6.2' in bot:
+    configs.extend(['glinst', 'glinst16'])
+
+  # CommandBuffer bot *only* runs the command_buffer config.
+  if 'CommandBuffer' in bot:
+    configs = ['commandbuffer']
+
+  # ANGLE bot *only* runs the angle configs
+  if 'ANGLE' in bot:
+    configs = ['angle_d3d11_es2',
+               'angle_d3d9_es2',
+               'angle_d3d11_es2_msaa4',
+               'angle_gl_es2']
+
+  # Vulkan bot *only* runs the vk config.
+  if 'Vulkan' in bot:
+    configs = ['vk']
+
+  args.append('--config')
+  args.extend(configs)
+
+  # Run tests, gms, and image decoding tests everywhere.
+  args.extend('--src tests gm image colorImage svg'.split(' '))
+
+  if 'GalaxyS' in bot:
+    args.extend(('--threads', '0'))
+
+  blacklisted = []
+  def blacklist(quad):
+    config, src, options, name = quad.split(' ') if type(quad) is str else quad
+    if config == '_' or config in configs:
+      blacklisted.extend([config, src, options, name])
+
+  # TODO: ???
+  blacklist('f16 _ _ dstreadshuffle')
+  blacklist('f16 image _ _')
+  blacklist('gpusrgb image _ _')
+  blacklist('glsrgb image _ _')
+
+  # Decoder tests are now performing gamma correct decodes.  This means
+  # that, when viewing the results, we need to perform a gamma correct
+  # encode to PNG.  Therefore, we run the image tests in srgb mode instead
+  # of 8888.
+  blacklist('8888 image _ _')
+
+  if 'Valgrind' in bot:
+    # These take 18+ hours to run.
+    blacklist('pdf gm _ fontmgr_iter')
+    blacklist('pdf _ _ PANO_20121023_214540.jpg')
+    blacklist('pdf skp _ worldjournal')
+    blacklist('pdf skp _ desk_baidu.skp')
+    blacklist('pdf skp _ desk_wikipedia.skp')
+    blacklist('_ svg _ _')
+
+  if 'iOS' in bot:
+    blacklist('gpu skp _ _')
+    blacklist('msaa skp _ _')
+    blacklist('msaa16 gm _ tilemodesProcess')
+
+  if 'Mac' in bot or 'iOS' in bot:
+    # CG fails on questionable bmps
+    blacklist('_ image gen_platf rgba32abf.bmp')
+    blacklist('_ image gen_platf rgb24prof.bmp')
+    blacklist('_ image gen_platf rgb24lprof.bmp')
+    blacklist('_ image gen_platf 8bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 4bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 32bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 24bpp-pixeldata-cropped.bmp')
+
+    # CG has unpredictable behavior on this questionable gif
+    # It's probably using uninitialized memory
+    blacklist('_ image gen_platf frame_larger_than_image.gif')
+
+    # CG has unpredictable behavior on incomplete pngs
+    # skbug.com/5774
+    blacklist('_ image gen_platf inc0.png')
+    blacklist('_ image gen_platf inc1.png')
+    blacklist('_ image gen_platf inc2.png')
+    blacklist('_ image gen_platf inc3.png')
+    blacklist('_ image gen_platf inc4.png')
+    blacklist('_ image gen_platf inc5.png')
+    blacklist('_ image gen_platf inc6.png')
+    blacklist('_ image gen_platf inc7.png')
+    blacklist('_ image gen_platf inc8.png')
+    blacklist('_ image gen_platf inc9.png')
+    blacklist('_ image gen_platf inc10.png')
+    blacklist('_ image gen_platf inc11.png')
+    blacklist('_ image gen_platf inc12.png')
+    blacklist('_ image gen_platf inc13.png')
+    blacklist('_ image gen_platf inc14.png')
+
+  # WIC fails on questionable bmps
+  if 'Win' in bot:
+    blacklist('_ image gen_platf rle8-height-negative.bmp')
+    blacklist('_ image gen_platf rle4-height-negative.bmp')
+    blacklist('_ image gen_platf pal8os2v2.bmp')
+    blacklist('_ image gen_platf pal8os2v2-16.bmp')
+    blacklist('_ image gen_platf rgba32abf.bmp')
+    blacklist('_ image gen_platf rgb24prof.bmp')
+    blacklist('_ image gen_platf rgb24lprof.bmp')
+    blacklist('_ image gen_platf 8bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 4bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 32bpp-pixeldata-cropped.bmp')
+    blacklist('_ image gen_platf 24bpp-pixeldata-cropped.bmp')
+    if 'x86_64' in bot and 'CPU' in bot:
+      # This GM triggers a SkSmallAllocator assert.
+      blacklist('_ gm _ composeshader_bitmap')
+
+  if 'Android' in bot or 'iOS' in bot:
+    # This test crashes the N9 (perhaps because of large malloc/frees). It also
+    # is fairly slow and not platform-specific. So we just disable it on all of
+    # Android and iOS. skia:5438
+    blacklist('_ test _ GrShape')
+
+  if 'Win8' in bot:
+    # bungeman: "Doesn't work on Windows anyway, produces unstable GMs with
+    # 'Unexpected error' from DirectWrite"
+    blacklist('_ gm _ fontscalerdistortable')
+    # skia:5636
+    blacklist('_ svg _ Nebraska-StateSeal.svg')
+
+  # skia:4095
+  bad_serialize_gms = ['bleed_image',
+                       'c_gms',
+                       'colortype',
+                       'colortype_xfermodes',
+                       'drawfilter',
+                       'fontmgr_bounds_0.75_0',
+                       'fontmgr_bounds_1_-0.25',
+                       'fontmgr_bounds',
+                       'fontmgr_match',
+                       'fontmgr_iter',
+                       'imagemasksubset']
+
+  # skia:5589
+  bad_serialize_gms.extend(['bitmapfilters',
+                            'bitmapshaders',
+                            'bleed',
+                            'bleed_alpha_bmp',
+                            'bleed_alpha_bmp_shader',
+                            'convex_poly_clip',
+                            'extractalpha',
+                            'filterbitmap_checkerboard_32_32_g8',
+                            'filterbitmap_image_mandrill_64',
+                            'shadows',
+                            'simpleaaclip_aaclip'])
+  # skia:5595
+  bad_serialize_gms.extend(['composeshader_bitmap',
+                            'scaled_tilemodes_npot',
+                            'scaled_tilemodes'])
+
+  # skia:5778
+  bad_serialize_gms.append('typefacerendering_pfaMac')
+  # skia:5942
+  bad_serialize_gms.append('parsedpaths')
+
+  # these use a custom image generator which doesn't serialize
+  bad_serialize_gms.append('ImageGeneratorExternal_rect')
+  bad_serialize_gms.append('ImageGeneratorExternal_shader')
+
+  for test in bad_serialize_gms:
+    blacklist(['serialize-8888', 'gm', '_', test])
+
+  if 'Mac' not in bot:
+    for test in ['bleed_alpha_image', 'bleed_alpha_image_shader']:
+      blacklist(['serialize-8888', 'gm', '_', test])
+  # It looks like we skip these only for out-of-memory concerns.
+  if 'Win' in bot or 'Android' in bot:
+    for test in ['verylargebitmap', 'verylarge_picture_image']:
+      blacklist(['serialize-8888', 'gm', '_', test])
+
+  # skia:4769
+  for test in ['drawfilter']:
+    blacklist([    'sp-8888', 'gm', '_', test])
+    blacklist([   'pic-8888', 'gm', '_', test])
+    blacklist(['2ndpic-8888', 'gm', '_', test])
+    blacklist([  'lite-8888', 'gm', '_', test])
+  # skia:4703
+  for test in ['image-cacherator-from-picture',
+               'image-cacherator-from-raster',
+               'image-cacherator-from-ctable']:
+    blacklist([       'sp-8888', 'gm', '_', test])
+    blacklist([      'pic-8888', 'gm', '_', test])
+    blacklist([   '2ndpic-8888', 'gm', '_', test])
+    blacklist(['serialize-8888', 'gm', '_', test])
+
+  # GM that requires raster-backed canvas
+  for test in ['gamut', 'complexclip4_bw', 'complexclip4_aa']:
+    blacklist([       'sp-8888', 'gm', '_', test])
+    blacklist([      'pic-8888', 'gm', '_', test])
+    blacklist([     'lite-8888', 'gm', '_', test])
+    blacklist([   '2ndpic-8888', 'gm', '_', test])
+    blacklist(['serialize-8888', 'gm', '_', test])
+
+  # GM that not support tiles_rt
+  for test in ['complexclip4_bw', 'complexclip4_aa']:
+    blacklist([ 'tiles_rt-8888', 'gm', '_', test])
+
+  # Extensions for RAW images
+  r = ["arw", "cr2", "dng", "nef", "nrw", "orf", "raf", "rw2", "pef", "srw",
+       "ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "RAF", "RW2", "PEF", "SRW"]
+
+  # skbug.com/4888
+  # Blacklist RAW images (and a few large PNGs) on GPU bots
+  # until we can resolve failures
+  if 'GPU' in bot:
+    blacklist('_ image _ interlaced1.png')
+    blacklist('_ image _ interlaced2.png')
+    blacklist('_ image _ interlaced3.png')
+    for raw_ext in r:
+      blacklist('_ image _ .%s' % raw_ext)
+
+  # Large image that overwhelms older Mac bots
+  if 'MacMini4.1-GPU' in bot:
+    blacklist('_ image _ abnormal.wbmp')
+    blacklist(['msaa16', 'gm', '_', 'blurcircles'])
+
+  if 'Nexus5' in bot:
+    # skia:5876
+    blacklist(['msaa4', 'gm', '_', 'encode-platform'])
+
+  match = []
+  if 'Valgrind' in bot: # skia:3021
+    match.append('~Threaded')
+
+  if 'AndroidOne' in bot:  # skia:4711
+    match.append('~WritePixels')
+
+  if 'NexusPlayer' in bot:
+    match.append('~ResourceCache')
+
+  if 'Nexus10' in bot:
+    match.append('~CopySurface') # skia:5509
+    match.append('~SRGBReadWritePixels') # skia:6097
+
+  if 'ANGLE' in bot and 'Debug' in bot:
+    match.append('~GLPrograms') # skia:4717
+
+  if 'MSAN' in bot:
+    match.extend(['~Once', '~Shared'])  # Not sure what's up with these tests.
+
+  if 'TSAN' in bot:
+    match.extend(['~ReadWriteAlpha'])   # Flaky on TSAN-covered on nvidia bots.
+    match.extend(['~RGBA4444TextureTest',  # Flakier than they are important.
+                  '~RGB565TextureTest'])
+
+  if 'Vulkan' in bot and 'Adreno' in bot:
+    # skia:5777
+    match.extend(['~XfermodeImageFilterCroppedInput',
+                  '~GrTextureStripAtlasFlush',
+                  '~CopySurface'])
+
+  if 'Vulkan' in bot and 'GTX1070' in bot and 'Win' in bot:
+    # skia:6092
+    match.append('~GPUMemorySize')
+
+  if 'IntelIris540' in bot and 'ANGLE' in bot:
+    match.append('~IntTexture') # skia:6086
+
+  if blacklisted:
+    args.append('--blacklist')
+    args.extend(blacklisted)
+
+  if match:
+    args.append('--match')
+    args.extend(match)
+
+  # These bots run out of memory running RAW codec tests. Do not run them in
+  # parallel
+  if ('NexusPlayer' in bot or 'Nexus5' in bot or 'Nexus9' in bot
+      or 'Win8-MSVC-ShuttleB' in bot):
+    args.append('--noRAW_threading')
+
+  return args
+
+
+def key_params(api):
+  """Build a unique key from the builder name (as a list).
+
+  E.g.  arch x86 gpu GeForce320M mode MacMini4.1 os Mac10.6
+  """
+  # Don't bother to include role, which is always Test.
+  # TryBots are uploaded elsewhere so they can use the same key.
+  blacklist = ['role', 'is_trybot']
+
+  flat = []
+  for k in sorted(api.vars.builder_cfg.keys()):
+    if k not in blacklist:
+      flat.append(k)
+      flat.append(api.vars.builder_cfg[k])
+  return flat
+
+
+def test_steps(api):
+  """Run the DM test."""
+  use_hash_file = False
+  if api.vars.upload_dm_results:
+    # This must run before we write anything into
+    # api.flavor.device_dirs.dm_dir or we may end up deleting our
+    # output on machines where they're the same.
+    api.flavor.create_clean_host_dir(api.vars.dm_dir)
+    host_dm_dir = str(api.vars.dm_dir)
+    device_dm_dir = str(api.flavor.device_dirs.dm_dir)
+    if host_dm_dir != device_dm_dir:
+      api.flavor.create_clean_device_dir(device_dm_dir)
+
+    # Obtain the list of already-generated hashes.
+    hash_filename = 'uninteresting_hashes.txt'
+
+    # Ensure that the tmp_dir exists.
+    api.run.run_once(api.file.makedirs,
+                           'tmp_dir',
+                           api.vars.tmp_dir,
+                           infra_step=True)
+
+    host_hashes_file = api.vars.tmp_dir.join(hash_filename)
+    hashes_file = api.flavor.device_path_join(
+        api.flavor.device_dirs.tmp_dir, hash_filename)
+    api.run(
+        api.python.inline,
+        'get uninteresting hashes',
+        program="""
+        import contextlib
+        import math
+        import socket
+        import sys
+        import time
+        import urllib2
+
+        HASHES_URL = 'https://gold.skia.org/_/hashes'
+        RETRIES = 5
+        TIMEOUT = 60
+        WAIT_BASE = 15
+
+        socket.setdefaulttimeout(TIMEOUT)
+        for retry in range(RETRIES):
+          try:
+            with contextlib.closing(
+                urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:
+              hashes = w.read()
+              with open(sys.argv[1], 'w') as f:
+                f.write(hashes)
+                break
+          except Exception as e:
+            print 'Failed to get uninteresting hashes from %s:' % HASHES_URL
+            print e
+            if retry == RETRIES:
+              raise
+            waittime = WAIT_BASE * math.pow(2, retry)
+            print 'Retry in %d seconds.' % waittime
+            time.sleep(waittime)
+        """,
+        args=[host_hashes_file],
+        cwd=api.vars.skia_dir,
+        abort_on_failure=False,
+        fail_build_on_failure=False,
+        infra_step=True)
+
+    if api.path.exists(host_hashes_file):
+      api.flavor.copy_file_to_device(host_hashes_file, hashes_file)
+      use_hash_file = True
+
+  # Run DM.
+  properties = [
+    'gitHash',      api.vars.got_revision,
+    'master',       api.vars.master_name,
+    'builder',      api.vars.builder_name,
+    'build_number', api.vars.build_number,
+  ]
+  if api.vars.is_trybot:
+    properties.extend([
+      'issue',         api.vars.issue,
+      'patchset',      api.vars.patchset,
+      'patch_storage', api.vars.patch_storage,
+    ])
+  if api.vars.no_buildbot:
+    properties.extend(['no_buildbot', 'True'])
+    properties.extend(['swarming_bot_id', api.vars.swarming_bot_id])
+    properties.extend(['swarming_task_id', api.vars.swarming_task_id])
+
+  args = [
+    'dm',
+    '--undefok',   # This helps branches that may not know new flags.
+    '--resourcePath', api.flavor.device_dirs.resource_dir,
+    '--skps', api.flavor.device_dirs.skp_dir,
+    '--images', api.flavor.device_path_join(
+        api.flavor.device_dirs.images_dir, 'dm'),
+    '--colorImages', api.flavor.device_path_join(
+        api.flavor.device_dirs.images_dir, 'colorspace'),
+    '--nameByHash',
+    '--properties'
+  ] + properties
+
+  args.extend(['--svgs', api.flavor.device_dirs.svg_dir])
+
+  args.append('--key')
+  args.extend(key_params(api))
+  if use_hash_file:
+    args.extend(['--uninterestingHashesFile', hashes_file])
+  if api.vars.upload_dm_results:
+    args.extend(['--writePath', api.flavor.device_dirs.dm_dir])
+
+  skip_flag = None
+  if api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
+    skip_flag = '--nogpu'
+  elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
+    skip_flag = '--nocpu'
+  if skip_flag:
+    args.append(skip_flag)
+  args.extend(dm_flags(api.vars.builder_name))
+
+  api.run(api.flavor.step, 'dm', cmd=args,
+          abort_on_failure=False,
+          env=api.vars.default_env)
+
+  if api.vars.upload_dm_results:
+    # Copy images and JSON to host machine if needed.
+    api.flavor.copy_directory_contents_to_host(
+        api.flavor.device_dirs.dm_dir, api.vars.dm_dir)
+
+  # See skia:2789.
+  if ('Valgrind' in api.vars.builder_name and
+      api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU'):
+    abandonGpuContext = list(args)
+    abandonGpuContext.append('--abandonGpuContext')
+    api.run(api.flavor.step, 'dm --abandonGpuContext',
+                  cmd=abandonGpuContext, abort_on_failure=False)
+    preAbandonGpuContext = list(args)
+    preAbandonGpuContext.append('--preAbandonGpuContext')
+    api.run(api.flavor.step, 'dm --preAbandonGpuContext',
+                  cmd=preAbandonGpuContext, abort_on_failure=False,
+                  env=api.vars.default_env)
+
+
+class TestApi(recipe_api.RecipeApi):
+  def run(self):
+    self.m.core.setup()
+    try:
+      self.m.flavor.install_everything()
+      test_steps(self.m)
+    finally:
+      self.m.flavor.cleanup_steps()
+    self.m.run.check_failure()
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-GalaxyS7-GPU-Adreno530-arm64-Debug-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-GalaxyS7-GPU-Adreno530-arm64-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-GalaxyS7-GPU-Adreno530-arm64-Debug-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-GalaxyS7-GPU-Adreno530-arm64-Debug-GN_Android.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
similarity index 99%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
index 6354557..9973f36 100644
--- a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
+++ b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
@@ -504,7 +504,7 @@
       "python",
       "-u",
       "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "set -x; /data/local/tmp/dm --undefok --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 master client.skia builder Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android build_number 5 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value MaliT604 extra_config GN_Android model Nexus10 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --nocpu --config 8888 srgb gpu gpudft gpusrgb msaa4 serialize-8888 tiles_rt-8888 pic-8888 --src tests gm image colorImage svg --blacklist gpusrgb image _ _ 8888 image _ _ _ test _ GrShape serialize-8888 gm _ bleed_image serialize-8888 gm _ c_gms serialize-8888 gm _ colortype serialize-8888 gm _ colortype_xfermodes serialize-8888 gm _ drawfilter serialize-8888 gm _ fontmgr_bounds_0.75_0 serialize-8888 gm _ fontmgr_bounds_1_-0.25 serialize-8888 gm _ fontmgr_bounds serialize-8888 gm _ fontmgr_match serialize-8888 gm _ fontmgr_iter serialize-8888 gm _ imagemasksubset serialize-8888 gm _ bitmapfilters serialize-8888 gm _ bitmapshaders serialize-8888 gm _ bleed serialize-8888 gm _ bleed_alpha_bmp serialize-8888 gm _ bleed_alpha_bmp_shader serialize-8888 gm _ convex_poly_clip serialize-8888 gm _ extractalpha serialize-8888 gm _ filterbitmap_checkerboard_32_32_g8 serialize-8888 gm _ filterbitmap_image_mandrill_64 serialize-8888 gm _ shadows serialize-8888 gm _ simpleaaclip_aaclip serialize-8888 gm _ composeshader_bitmap serialize-8888 gm _ scaled_tilemodes_npot serialize-8888 gm _ scaled_tilemodes serialize-8888 gm _ typefacerendering_pfaMac serialize-8888 gm _ parsedpaths serialize-8888 gm _ ImageGeneratorExternal_rect serialize-8888 gm _ ImageGeneratorExternal_shader serialize-8888 gm _ bleed_alpha_image serialize-8888 gm _ bleed_alpha_image_shader serialize-8888 gm _ verylargebitmap serialize-8888 gm _ verylarge_picture_image pic-8888 gm _ drawfilter pic-8888 gm _ image-cacherator-from-picture serialize-8888 gm _ image-cacherator-from-picture pic-8888 gm _ image-cacherator-from-raster serialize-8888 gm _ image-cacherator-from-raster pic-8888 gm _ image-cacherator-from-ctable serialize-8888 gm _ image-cacherator-from-ctable pic-8888 gm _ gamut serialize-8888 gm _ gamut pic-8888 gm _ complexclip4_bw serialize-8888 gm _ complexclip4_bw pic-8888 gm _ complexclip4_aa serialize-8888 gm _ complexclip4_aa tiles_rt-8888 gm _ complexclip4_bw tiles_rt-8888 gm _ complexclip4_aa _ 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 --match ~CopySurface; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --undefok --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 master client.skia builder Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android build_number 5 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value MaliT604 extra_config GN_Android model Nexus10 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --nocpu --config 8888 srgb gpu gpudft gpusrgb msaa4 serialize-8888 tiles_rt-8888 pic-8888 --src tests gm image colorImage svg --blacklist gpusrgb image _ _ 8888 image _ _ _ test _ GrShape serialize-8888 gm _ bleed_image serialize-8888 gm _ c_gms serialize-8888 gm _ colortype serialize-8888 gm _ colortype_xfermodes serialize-8888 gm _ drawfilter serialize-8888 gm _ fontmgr_bounds_0.75_0 serialize-8888 gm _ fontmgr_bounds_1_-0.25 serialize-8888 gm _ fontmgr_bounds serialize-8888 gm _ fontmgr_match serialize-8888 gm _ fontmgr_iter serialize-8888 gm _ imagemasksubset serialize-8888 gm _ bitmapfilters serialize-8888 gm _ bitmapshaders serialize-8888 gm _ bleed serialize-8888 gm _ bleed_alpha_bmp serialize-8888 gm _ bleed_alpha_bmp_shader serialize-8888 gm _ convex_poly_clip serialize-8888 gm _ extractalpha serialize-8888 gm _ filterbitmap_checkerboard_32_32_g8 serialize-8888 gm _ filterbitmap_image_mandrill_64 serialize-8888 gm _ shadows serialize-8888 gm _ simpleaaclip_aaclip serialize-8888 gm _ composeshader_bitmap serialize-8888 gm _ scaled_tilemodes_npot serialize-8888 gm _ scaled_tilemodes serialize-8888 gm _ typefacerendering_pfaMac serialize-8888 gm _ parsedpaths serialize-8888 gm _ ImageGeneratorExternal_rect serialize-8888 gm _ ImageGeneratorExternal_shader serialize-8888 gm _ bleed_alpha_image serialize-8888 gm _ bleed_alpha_image_shader serialize-8888 gm _ verylargebitmap serialize-8888 gm _ verylarge_picture_image pic-8888 gm _ drawfilter pic-8888 gm _ image-cacherator-from-picture serialize-8888 gm _ image-cacherator-from-picture pic-8888 gm _ image-cacherator-from-raster serialize-8888 gm _ image-cacherator-from-raster pic-8888 gm _ image-cacherator-from-ctable serialize-8888 gm _ image-cacherator-from-ctable pic-8888 gm _ gamut serialize-8888 gm _ gamut pic-8888 gm _ complexclip4_bw serialize-8888 gm _ complexclip4_bw pic-8888 gm _ complexclip4_aa serialize-8888 gm _ complexclip4_aa tiles_rt-8888 gm _ complexclip4_bw tiles_rt-8888 gm _ complexclip4_aa _ 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 --match ~CopySurface ~SRGBReadWritePixels; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "name": "write dm.sh"
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android.json
similarity index 88%
copy from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
copy to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android.json
index 6354557..c78cf86 100644
--- a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android.json
+++ b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android.json
@@ -504,7 +504,7 @@
       "python",
       "-u",
       "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
-      "set -x; /data/local/tmp/dm --undefok --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 master client.skia builder Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android build_number 5 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value MaliT604 extra_config GN_Android model Nexus10 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --nocpu --config 8888 srgb gpu gpudft gpusrgb msaa4 serialize-8888 tiles_rt-8888 pic-8888 --src tests gm image colorImage svg --blacklist gpusrgb image _ _ 8888 image _ _ _ test _ GrShape serialize-8888 gm _ bleed_image serialize-8888 gm _ c_gms serialize-8888 gm _ colortype serialize-8888 gm _ colortype_xfermodes serialize-8888 gm _ drawfilter serialize-8888 gm _ fontmgr_bounds_0.75_0 serialize-8888 gm _ fontmgr_bounds_1_-0.25 serialize-8888 gm _ fontmgr_bounds serialize-8888 gm _ fontmgr_match serialize-8888 gm _ fontmgr_iter serialize-8888 gm _ imagemasksubset serialize-8888 gm _ bitmapfilters serialize-8888 gm _ bitmapshaders serialize-8888 gm _ bleed serialize-8888 gm _ bleed_alpha_bmp serialize-8888 gm _ bleed_alpha_bmp_shader serialize-8888 gm _ convex_poly_clip serialize-8888 gm _ extractalpha serialize-8888 gm _ filterbitmap_checkerboard_32_32_g8 serialize-8888 gm _ filterbitmap_image_mandrill_64 serialize-8888 gm _ shadows serialize-8888 gm _ simpleaaclip_aaclip serialize-8888 gm _ composeshader_bitmap serialize-8888 gm _ scaled_tilemodes_npot serialize-8888 gm _ scaled_tilemodes serialize-8888 gm _ typefacerendering_pfaMac serialize-8888 gm _ parsedpaths serialize-8888 gm _ ImageGeneratorExternal_rect serialize-8888 gm _ ImageGeneratorExternal_shader serialize-8888 gm _ bleed_alpha_image serialize-8888 gm _ bleed_alpha_image_shader serialize-8888 gm _ verylargebitmap serialize-8888 gm _ verylarge_picture_image pic-8888 gm _ drawfilter pic-8888 gm _ image-cacherator-from-picture serialize-8888 gm _ image-cacherator-from-picture pic-8888 gm _ image-cacherator-from-raster serialize-8888 gm _ image-cacherator-from-raster pic-8888 gm _ image-cacherator-from-ctable serialize-8888 gm _ image-cacherator-from-ctable pic-8888 gm _ gamut serialize-8888 gm _ gamut pic-8888 gm _ complexclip4_bw serialize-8888 gm _ complexclip4_bw pic-8888 gm _ complexclip4_aa serialize-8888 gm _ complexclip4_aa tiles_rt-8888 gm _ complexclip4_bw tiles_rt-8888 gm _ complexclip4_aa _ 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 --match ~CopySurface; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --undefok --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 master client.skia builder Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android build_number 5 --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Release cpu_or_gpu GPU cpu_or_gpu_value Adreno330 extra_config Android model Nexus5 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --nocpu --config 8888 srgb gpu gpudft gpusrgb msaa4 serialize-8888 tiles_rt-8888 pic-8888 --src tests gm image colorImage svg --blacklist gpusrgb image _ _ 8888 image _ _ _ test _ GrShape serialize-8888 gm _ bleed_image serialize-8888 gm _ c_gms serialize-8888 gm _ colortype serialize-8888 gm _ colortype_xfermodes serialize-8888 gm _ drawfilter serialize-8888 gm _ fontmgr_bounds_0.75_0 serialize-8888 gm _ fontmgr_bounds_1_-0.25 serialize-8888 gm _ fontmgr_bounds serialize-8888 gm _ fontmgr_match serialize-8888 gm _ fontmgr_iter serialize-8888 gm _ imagemasksubset serialize-8888 gm _ bitmapfilters serialize-8888 gm _ bitmapshaders serialize-8888 gm _ bleed serialize-8888 gm _ bleed_alpha_bmp serialize-8888 gm _ bleed_alpha_bmp_shader serialize-8888 gm _ convex_poly_clip serialize-8888 gm _ extractalpha serialize-8888 gm _ filterbitmap_checkerboard_32_32_g8 serialize-8888 gm _ filterbitmap_image_mandrill_64 serialize-8888 gm _ shadows serialize-8888 gm _ simpleaaclip_aaclip serialize-8888 gm _ composeshader_bitmap serialize-8888 gm _ scaled_tilemodes_npot serialize-8888 gm _ scaled_tilemodes serialize-8888 gm _ typefacerendering_pfaMac serialize-8888 gm _ parsedpaths serialize-8888 gm _ ImageGeneratorExternal_rect serialize-8888 gm _ ImageGeneratorExternal_shader serialize-8888 gm _ bleed_alpha_image serialize-8888 gm _ bleed_alpha_image_shader serialize-8888 gm _ verylargebitmap serialize-8888 gm _ verylarge_picture_image pic-8888 gm _ drawfilter pic-8888 gm _ image-cacherator-from-picture serialize-8888 gm _ image-cacherator-from-picture pic-8888 gm _ image-cacherator-from-raster serialize-8888 gm _ image-cacherator-from-raster pic-8888 gm _ image-cacherator-from-ctable serialize-8888 gm _ image-cacherator-from-ctable pic-8888 gm _ gamut serialize-8888 gm _ gamut pic-8888 gm _ complexclip4_bw serialize-8888 gm _ complexclip4_bw pic-8888 gm _ complexclip4_aa serialize-8888 gm _ complexclip4_aa tiles_rt-8888 gm _ complexclip4_bw tiles_rt-8888 gm _ complexclip4_aa _ 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 msaa4 gm _ encode-platform --noRAW_threading; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "name": "write dm.sh"
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
new file mode 100644
index 0000000..0a14eb6
--- /dev/null
+++ b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
@@ -0,0 +1,527 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SKP VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SVG VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os, sys\nfrom common import chromium_utils # Error? See https://crbug.com/584783.\n\n\nif os.path.exists(sys.argv[1]):\n  chromium_utils.RemoveDirectory(sys.argv[1])\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]/dm"
+    ],
+    "env": {
+      "PYTHONPATH": "[START_DIR]/skia/infra/bots/.recipe_deps/build/scripts"
+    },
+    "name": "rmtree dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os, sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@from common import chromium_utils # Error? See https://crbug.com/584783.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@if os.path.exists(sys.argv[1]):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  chromium_utils.RemoveDirectory(sys.argv[1])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]/dm",
+      "511"
+    ],
+    "name": "makedirs dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[START_DIR]/tmp",
+      "511"
+    ],
+    "name": "makedirs tmp_dir",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = 'https://gold.skia.org/_/hashes'\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[1], '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",
+      "[START_DIR]/tmp/uninteresting_hashes.txt"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "env": {
+      "BUILDTYPE": "Debug",
+      "CHROME_HEADLESS": "1",
+      "SKIA_OUT": "[START_DIR]/out"
+    },
+    "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 = 'https://gold.skia.org/_/hashes'@@@",
+      "@@@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[1], '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": [
+      "catchsegv",
+      "[START_DIR]/out/Debug/dm",
+      "--undefok",
+      "--resourcePath",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/dm",
+      "--colorImages",
+      "[START_DIR]/skimage/colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "master",
+      "client.skia",
+      "builder",
+      "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug",
+      "build_number",
+      "5",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--key",
+      "arch",
+      "x86",
+      "compiler",
+      "GCC",
+      "configuration",
+      "Debug",
+      "cpu_or_gpu",
+      "CPU",
+      "cpu_or_gpu_value",
+      "AVX2",
+      "model",
+      "GCE",
+      "os",
+      "Ubuntu",
+      "--uninterestingHashesFile",
+      "[START_DIR]/tmp/uninteresting_hashes.txt",
+      "--writePath",
+      "[CUSTOM_[SWARM_OUT_DIR]]/dm",
+      "--nogpu",
+      "--threads",
+      "4",
+      "--config",
+      "8888",
+      "srgb",
+      "gpu",
+      "gpudft",
+      "gpusrgb",
+      "pdf",
+      "msaa16",
+      "565",
+      "f16",
+      "sp-8888",
+      "2ndpic-8888",
+      "lite-8888",
+      "serialize-8888",
+      "tiles_rt-8888",
+      "pic-8888",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "svg",
+      "--blacklist",
+      "f16",
+      "_",
+      "_",
+      "dstreadshuffle",
+      "f16",
+      "image",
+      "_",
+      "_",
+      "gpusrgb",
+      "image",
+      "_",
+      "_",
+      "8888",
+      "image",
+      "_",
+      "_",
+      "serialize-8888",
+      "gm",
+      "_",
+      "bleed_image",
+      "serialize-8888",
+      "gm",
+      "_",
+      "c_gms",
+      "serialize-8888",
+      "gm",
+      "_",
+      "colortype",
+      "serialize-8888",
+      "gm",
+      "_",
+      "colortype_xfermodes",
+      "serialize-8888",
+      "gm",
+      "_",
+      "drawfilter",
+      "serialize-8888",
+      "gm",
+      "_",
+      "fontmgr_bounds_0.75_0",
+      "serialize-8888",
+      "gm",
+      "_",
+      "fontmgr_bounds_1_-0.25",
+      "serialize-8888",
+      "gm",
+      "_",
+      "fontmgr_bounds",
+      "serialize-8888",
+      "gm",
+      "_",
+      "fontmgr_match",
+      "serialize-8888",
+      "gm",
+      "_",
+      "fontmgr_iter",
+      "serialize-8888",
+      "gm",
+      "_",
+      "imagemasksubset",
+      "serialize-8888",
+      "gm",
+      "_",
+      "bitmapfilters",
+      "serialize-8888",
+      "gm",
+      "_",
+      "bitmapshaders",
+      "serialize-8888",
+      "gm",
+      "_",
+      "bleed",
+      "serialize-8888",
+      "gm",
+      "_",
+      "bleed_alpha_bmp",
+      "serialize-8888",
+      "gm",
+      "_",
+      "bleed_alpha_bmp_shader",
+      "serialize-8888",
+      "gm",
+      "_",
+      "convex_poly_clip",
+      "serialize-8888",
+      "gm",
+      "_",
+      "extractalpha",
+      "serialize-8888",
+      "gm",
+      "_",
+      "filterbitmap_checkerboard_32_32_g8",
+      "serialize-8888",
+      "gm",
+      "_",
+      "filterbitmap_image_mandrill_64",
+      "serialize-8888",
+      "gm",
+      "_",
+      "shadows",
+      "serialize-8888",
+      "gm",
+      "_",
+      "simpleaaclip_aaclip",
+      "serialize-8888",
+      "gm",
+      "_",
+      "composeshader_bitmap",
+      "serialize-8888",
+      "gm",
+      "_",
+      "scaled_tilemodes_npot",
+      "serialize-8888",
+      "gm",
+      "_",
+      "scaled_tilemodes",
+      "serialize-8888",
+      "gm",
+      "_",
+      "typefacerendering_pfaMac",
+      "serialize-8888",
+      "gm",
+      "_",
+      "parsedpaths",
+      "serialize-8888",
+      "gm",
+      "_",
+      "ImageGeneratorExternal_rect",
+      "serialize-8888",
+      "gm",
+      "_",
+      "ImageGeneratorExternal_shader",
+      "serialize-8888",
+      "gm",
+      "_",
+      "bleed_alpha_image",
+      "serialize-8888",
+      "gm",
+      "_",
+      "bleed_alpha_image_shader",
+      "sp-8888",
+      "gm",
+      "_",
+      "drawfilter",
+      "pic-8888",
+      "gm",
+      "_",
+      "drawfilter",
+      "2ndpic-8888",
+      "gm",
+      "_",
+      "drawfilter",
+      "lite-8888",
+      "gm",
+      "_",
+      "drawfilter",
+      "sp-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-picture",
+      "pic-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-picture",
+      "2ndpic-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-picture",
+      "serialize-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-picture",
+      "sp-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-raster",
+      "pic-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-raster",
+      "2ndpic-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-raster",
+      "serialize-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-raster",
+      "sp-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-ctable",
+      "pic-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-ctable",
+      "2ndpic-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-ctable",
+      "serialize-8888",
+      "gm",
+      "_",
+      "image-cacherator-from-ctable",
+      "sp-8888",
+      "gm",
+      "_",
+      "gamut",
+      "pic-8888",
+      "gm",
+      "_",
+      "gamut",
+      "lite-8888",
+      "gm",
+      "_",
+      "gamut",
+      "2ndpic-8888",
+      "gm",
+      "_",
+      "gamut",
+      "serialize-8888",
+      "gm",
+      "_",
+      "gamut",
+      "sp-8888",
+      "gm",
+      "_",
+      "complexclip4_bw",
+      "pic-8888",
+      "gm",
+      "_",
+      "complexclip4_bw",
+      "lite-8888",
+      "gm",
+      "_",
+      "complexclip4_bw",
+      "2ndpic-8888",
+      "gm",
+      "_",
+      "complexclip4_bw",
+      "serialize-8888",
+      "gm",
+      "_",
+      "complexclip4_bw",
+      "sp-8888",
+      "gm",
+      "_",
+      "complexclip4_aa",
+      "pic-8888",
+      "gm",
+      "_",
+      "complexclip4_aa",
+      "lite-8888",
+      "gm",
+      "_",
+      "complexclip4_aa",
+      "2ndpic-8888",
+      "gm",
+      "_",
+      "complexclip4_aa",
+      "serialize-8888",
+      "gm",
+      "_",
+      "complexclip4_aa",
+      "tiles_rt-8888",
+      "gm",
+      "_",
+      "complexclip4_bw",
+      "tiles_rt-8888",
+      "gm",
+      "_",
+      "complexclip4_aa"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "dm"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE.json
new file mode 100644
index 0000000..4472f54
--- /dev/null
+++ b/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE.json
@@ -0,0 +1,382 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skp\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SKP VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SKP_VERSION"
+    ],
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skimage\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SK_IMAGE_VERSION"
+    ],
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\svg\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SVG VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SVG_VERSION"
+    ],
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os, sys\nfrom common import chromium_utils # Error? See https://crbug.com/584783.\n\n\nif os.path.exists(sys.argv[1]):\n  chromium_utils.RemoveDirectory(sys.argv[1])\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\dm"
+    ],
+    "env": {
+      "PYTHONPATH": "[START_DIR]\\skia\\infra\\bots\\.recipe_deps\\build\\scripts"
+    },
+    "name": "rmtree dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os, sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@from common import chromium_utils # Error? See https://crbug.com/584783.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@if os.path.exists(sys.argv[1]):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  chromium_utils.RemoveDirectory(sys.argv[1])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\dm",
+      "511"
+    ],
+    "name": "makedirs dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[START_DIR]\\tmp",
+      "511"
+    ],
+    "name": "makedirs tmp_dir",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = 'https://gold.skia.org/_/hashes'\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[1], '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",
+      "[START_DIR]\\tmp\\uninteresting_hashes.txt"
+    ],
+    "cwd": "[START_DIR]\\skia",
+    "env": {
+      "BUILDTYPE": "Debug_x64",
+      "CHROME_HEADLESS": "1",
+      "SKIA_OUT": "[START_DIR]\\out"
+    },
+    "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 = 'https://gold.skia.org/_/hashes'@@@",
+      "@@@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[1], '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": [
+      "[START_DIR]\\out\\Debug_x64\\dm",
+      "--undefok",
+      "--resourcePath",
+      "[START_DIR]\\skia\\resources",
+      "--skps",
+      "[START_DIR]\\skp",
+      "--images",
+      "[START_DIR]\\skimage\\dm",
+      "--colorImages",
+      "[START_DIR]\\skimage\\colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "master",
+      "client.skia",
+      "builder",
+      "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE",
+      "build_number",
+      "5",
+      "--svgs",
+      "[START_DIR]\\svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "MSVC",
+      "configuration",
+      "Debug",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelIris540",
+      "extra_config",
+      "ANGLE",
+      "model",
+      "NUC",
+      "os",
+      "Win10",
+      "--uninterestingHashesFile",
+      "[START_DIR]\\tmp\\uninteresting_hashes.txt",
+      "--writePath",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\dm",
+      "--nocpu",
+      "--config",
+      "angle_d3d11_es2",
+      "angle_d3d9_es2",
+      "angle_d3d11_es2_msaa4",
+      "angle_gl_es2",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "svg",
+      "--blacklist",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "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",
+      "_",
+      "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",
+      "--match",
+      "~GLPrograms",
+      "~IntTexture"
+    ],
+    "cwd": "[START_DIR]\\skia",
+    "name": "dm"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan.json
diff --git a/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan.json
new file mode 100644
index 0000000..0e17c8c
--- /dev/null
+++ b/infra/bots/recipe_modules/sktest/example.expected/Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan.json
@@ -0,0 +1,378 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skp\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SKP VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SKP_VERSION"
+    ],
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skimage\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SK_IMAGE_VERSION"
+    ],
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\svg\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SVG VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]\\tmp\\SVG_VERSION"
+    ],
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os, sys\nfrom common import chromium_utils # Error? See https://crbug.com/584783.\n\n\nif os.path.exists(sys.argv[1]):\n  chromium_utils.RemoveDirectory(sys.argv[1])\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\dm"
+    ],
+    "env": {
+      "PYTHONPATH": "[START_DIR]\\skia\\infra\\bots\\.recipe_deps\\build\\scripts"
+    },
+    "name": "rmtree dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os, sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@from common import chromium_utils # Error? See https://crbug.com/584783.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@if os.path.exists(sys.argv[1]):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  chromium_utils.RemoveDirectory(sys.argv[1])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\dm",
+      "511"
+    ],
+    "name": "makedirs dm",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[START_DIR]\\tmp",
+      "511"
+    ],
+    "name": "makedirs tmp_dir",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = 'https://gold.skia.org/_/hashes'\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[1], '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",
+      "[START_DIR]\\tmp\\uninteresting_hashes.txt"
+    ],
+    "cwd": "[START_DIR]\\skia",
+    "env": {
+      "BUILDTYPE": "Debug_x64",
+      "CHROME_HEADLESS": "1",
+      "SKIA_OUT": "[START_DIR]\\out"
+    },
+    "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 = 'https://gold.skia.org/_/hashes'@@@",
+      "@@@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[1], '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": [
+      "[START_DIR]\\out\\Debug_x64\\dm",
+      "--undefok",
+      "--resourcePath",
+      "[START_DIR]\\skia\\resources",
+      "--skps",
+      "[START_DIR]\\skp",
+      "--images",
+      "[START_DIR]\\skimage\\dm",
+      "--colorImages",
+      "[START_DIR]\\skimage\\colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "master",
+      "client.skia",
+      "builder",
+      "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan",
+      "build_number",
+      "5",
+      "--svgs",
+      "[START_DIR]\\svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "MSVC",
+      "configuration",
+      "Debug",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "GTX1070",
+      "extra_config",
+      "Vulkan",
+      "model",
+      "ZBOX",
+      "os",
+      "Win10",
+      "--uninterestingHashesFile",
+      "[START_DIR]\\tmp\\uninteresting_hashes.txt",
+      "--writePath",
+      "[CUSTOM_[SWARM_OUT_DIR]]\\dm",
+      "--nocpu",
+      "--config",
+      "vk",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "svg",
+      "--blacklist",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "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",
+      "_",
+      "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",
+      "--match",
+      "~GPUMemorySize"
+    ],
+    "cwd": "[START_DIR]\\skia",
+    "name": "dm"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json b/infra/bots/recipe_modules/sktest/example.expected/Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE.json
diff --git a/infra/bots/recipes/swarm_test.expected/Test-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug.json b/infra/bots/recipe_modules/sktest/example.expected/Test-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/Test-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug.json
rename to infra/bots/recipe_modules/sktest/example.expected/Test-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug.json
diff --git a/infra/bots/recipes/swarm_test.expected/big_issue_number.json b/infra/bots/recipe_modules/sktest/example.expected/big_issue_number.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/big_issue_number.json
rename to infra/bots/recipe_modules/sktest/example.expected/big_issue_number.json
diff --git a/infra/bots/recipes/swarm_test.expected/failed_dm.json b/infra/bots/recipe_modules/sktest/example.expected/failed_dm.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/failed_dm.json
rename to infra/bots/recipe_modules/sktest/example.expected/failed_dm.json
diff --git a/infra/bots/recipes/swarm_test.expected/failed_get_hashes.json b/infra/bots/recipe_modules/sktest/example.expected/failed_get_hashes.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/failed_get_hashes.json
rename to infra/bots/recipe_modules/sktest/example.expected/failed_get_hashes.json
diff --git a/infra/bots/recipes/swarm_test.expected/missing_SKP_VERSION_device.json b/infra/bots/recipe_modules/sktest/example.expected/missing_SKP_VERSION_device.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/missing_SKP_VERSION_device.json
rename to infra/bots/recipe_modules/sktest/example.expected/missing_SKP_VERSION_device.json
diff --git a/infra/bots/recipes/swarm_test.expected/nobuildbot.json b/infra/bots/recipe_modules/sktest/example.expected/nobuildbot.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/nobuildbot.json
rename to infra/bots/recipe_modules/sktest/example.expected/nobuildbot.json
diff --git a/infra/bots/recipes/swarm_test.expected/recipe_with_gerrit_patch.json b/infra/bots/recipe_modules/sktest/example.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipes/swarm_test.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipe_modules/sktest/example.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipe_modules/sktest/example.py b/infra/bots/recipe_modules/sktest/example.py
new file mode 100644
index 0000000..7ea7d6c
--- /dev/null
+++ b/infra/bots/recipe_modules/sktest/example.py
@@ -0,0 +1,225 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Example recipe w/ coverage.
+
+
+DEPS = [
+  'recipe_engine/path',
+  'recipe_engine/platform',
+  'recipe_engine/properties',
+  'recipe_engine/raw_io',
+  'sktest',
+]
+
+
+TEST_BUILDERS = {
+  'client.skia': {
+    'skiabot-linux-swarm-000': [
+      'Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android',
+      'Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android',
+      'Test-Android-Clang-GalaxyS7-GPU-Adreno530-arm64-Debug-GN_Android',
+      'Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android',
+      'Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android',
+      'Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-Android',
+      'Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android',
+      'Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan',
+      'Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android',
+      'Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android',
+      'Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android',
+      'Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug',
+      'Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug',
+      'Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared',
+      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN',
+      'Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind',
+      'Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE',
+      'Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan',
+      'Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan',
+      'Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot',
+      'Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE',
+      'Test-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug',
+    ],
+  },
+}
+
+
+def RunSteps(api):
+  api.sktest.run()
+
+
+def GenTests(api):
+  for mastername, slaves in TEST_BUILDERS.iteritems():
+    for slavename, builders_by_slave in slaves.iteritems():
+      for builder in builders_by_slave:
+        test = (
+          api.test(builder) +
+          api.properties(buildername=builder,
+                         mastername=mastername,
+                         slavename=slavename,
+                         buildnumber=5,
+                         revision='abc123',
+                         path_config='kitchen',
+                         swarm_out_dir='[SWARM_OUT_DIR]') +
+          api.path.exists(
+              api.path['start_dir'].join('skia'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skimage', 'VERSION'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'skp', 'VERSION'),
+              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                           'svg', 'VERSION'),
+              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+          )
+        )
+        if 'Trybot' in builder:
+          test += api.properties(issue=500,
+                                 patchset=1,
+                                 rietveld='https://codereview.chromium.org')
+        if 'Win' in builder:
+          test += api.platform('win', 64)
+
+
+        yield test
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
+  yield (
+    api.test('failed_dm') +
+    api.properties(buildername=builder,
+                   mastername='client.skia',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=6,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.step_data('dm', retcode=1)
+  )
+
+  builder = 'Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android'
+  yield (
+    api.test('failed_get_hashes') +
+    api.properties(buildername=builder,
+                   mastername='client.skia',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=6,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.step_data('get uninteresting hashes', retcode=1)
+  )
+
+  builder = 'Test-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug'
+  yield (
+    api.test('missing_SKP_VERSION_device') +
+    api.properties(buildername=builder,
+                   mastername='client.skia',
+                   slavename='skiabot-linux-swarm-000',
+                   buildnumber=6,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.step_data('read SKP_VERSION', retcode=1)
+  )
+
+  builder = 'Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot'
+  yield (
+    api.test('big_issue_number') +
+    api.properties(buildername=builder,
+                     mastername='client.skia.compile',
+                     slavename='skiabot-linux-swarm-000',
+                     buildnumber=5,
+                     revision='abc123',
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]',
+                     rietveld='https://codereview.chromium.org',
+                     patchset=1,
+                     issue=2147533002L) +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skimage', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'skp', 'VERSION'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                     'svg', 'VERSION'),
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    ) +
+    api.platform('win', 64)
+  )
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug-Trybot'
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=builder,
+          mastername='client.skia',
+          slavename='skiabot-linux-swarm-000',
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      )
+  )
+
+  yield (
+      api.test('nobuildbot') +
+      api.properties(
+          buildername=builder,
+          mastername='client.skia',
+          slavename='skiabot-linux-swarm-000',
+          buildnumber=5,
+          path_config='kitchen',
+          swarm_out_dir='[SWARM_OUT_DIR]',
+          revision='abc123',
+          nobuildbot='True',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      ) +
+      api.step_data('get swarming bot id',
+          stdout=api.raw_io.output('skia-bot-123')) +
+      api.step_data('get swarming task id', stdout=api.raw_io.output('123456'))
+  )
diff --git a/infra/bots/recipe_modules/upload_dm_results/__init__.py b/infra/bots/recipe_modules/upload_dm_results/__init__.py
new file mode 100644
index 0000000..df2e005
--- /dev/null
+++ b/infra/bots/recipe_modules/upload_dm_results/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+  'build/file',
+  'recipe_engine/json',
+  'recipe_engine/path',
+  'recipe_engine/properties',
+  'recipe_engine/shutil',
+  'recipe_engine/step',
+  'recipe_engine/time',
+]
diff --git a/infra/bots/recipe_modules/upload_dm_results/api.py b/infra/bots/recipe_modules/upload_dm_results/api.py
new file mode 100644
index 0000000..cc395fd
--- /dev/null
+++ b/infra/bots/recipe_modules/upload_dm_results/api.py
@@ -0,0 +1,91 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Recipe for uploading DM results.
+
+
+
+import calendar
+
+from recipe_engine import recipe_api
+
+
+DM_JSON = 'dm.json'
+GS_BUCKET = 'gs://skia-infra-gm'
+UPLOAD_ATTEMPTS = 5
+VERBOSE_LOG = 'verbose.log'
+
+
+class UploadDmResultsApi(recipe_api.RecipeApi):
+  def cp(self, name, src, dst, extra_args=None):
+    cmd = ['gsutil', 'cp']
+    if extra_args:
+      cmd.extend(extra_args)
+    cmd.extend([src, dst])
+
+    name = 'upload %s' % name
+    for i in xrange(UPLOAD_ATTEMPTS):
+      step_name = name
+      if i > 0:
+        step_name += ' (attempt %d)' % (i+1)
+      try:
+        self.m.step(step_name, cmd=cmd)
+        break
+      except self.m.step.StepFailure:
+        if i == UPLOAD_ATTEMPTS - 1:
+          raise
+
+  def run(self):
+    builder_name = self.m.properties['buildername']
+    revision = self.m.properties['revision']
+
+    results_dir = self.m.path['start_dir'].join('dm')
+
+    # Move dm.json and verbose.log to their own directory.
+    json_file = results_dir.join(DM_JSON)
+    log_file = results_dir.join(VERBOSE_LOG)
+    tmp_dir = self.m.path['start_dir'].join('tmp_upload')
+    self.m.shutil.makedirs('tmp dir', tmp_dir, infra_step=True)
+    self.m.shutil.copy('copy dm.json', json_file, tmp_dir)
+    self.m.shutil.copy('copy verbose.log', log_file, tmp_dir)
+    self.m.shutil.remove('rm old dm.json', json_file)
+    self.m.shutil.remove('rm old verbose.log', log_file)
+
+    # Upload the images.
+    image_dest_path = '/'.join((GS_BUCKET, 'dm-images-v1'))
+    files_to_upload = self.m.file.glob(
+        'find images',
+        results_dir.join('*'),
+        test_data=['someimage.png'],
+        infra_step=True)
+    if len(files_to_upload) > 0:
+      self.cp('images', results_dir.join('*'), image_dest_path)
+
+    # Upload the JSON summary and verbose.log.
+    now = self.m.time.utcnow()
+    summary_dest_path = '/'.join([
+        'dm-json-v1',
+        str(now.year ).zfill(4),
+        str(now.month).zfill(2),
+        str(now.day  ).zfill(2),
+        str(now.hour ).zfill(2),
+        revision,
+        builder_name,
+        str(int(calendar.timegm(now.utctimetuple())))])
+
+    # Trybot results are further siloed by issue/patchset.
+    issue = str(self.m.properties.get('issue', ''))
+    patchset = str(self.m.properties.get('patchset', ''))
+    if self.m.properties.get('patch_storage', '') == 'gerrit':
+      issue = str(self.m.properties['patch_issue'])
+      patchset = str(self.m.properties['patch_set'])
+    if issue and patchset:
+      summary_dest_path = '/'.join((
+          'trybot', summary_dest_path, issue, patchset))
+
+    summary_dest_path = '/'.join((GS_BUCKET, summary_dest_path))
+
+    self.cp('JSON and logs', tmp_dir.join('*'), summary_dest_path,
+       ['-z', 'json,log'])
diff --git a/infra/bots/recipes/upload_dm_results.expected/failed_all.json b/infra/bots/recipe_modules/upload_dm_results/example.expected/failed_all.json
similarity index 100%
rename from infra/bots/recipes/upload_dm_results.expected/failed_all.json
rename to infra/bots/recipe_modules/upload_dm_results/example.expected/failed_all.json
diff --git a/infra/bots/recipes/upload_dm_results.expected/failed_once.json b/infra/bots/recipe_modules/upload_dm_results/example.expected/failed_once.json
similarity index 100%
rename from infra/bots/recipes/upload_dm_results.expected/failed_once.json
rename to infra/bots/recipe_modules/upload_dm_results/example.expected/failed_once.json
diff --git a/infra/bots/recipes/upload_dm_results.expected/normal_bot.json b/infra/bots/recipe_modules/upload_dm_results/example.expected/normal_bot.json
similarity index 100%
copy from infra/bots/recipes/upload_dm_results.expected/normal_bot.json
copy to infra/bots/recipe_modules/upload_dm_results/example.expected/normal_bot.json
diff --git a/infra/bots/recipes/upload_dm_results.expected/recipe_with_gerrit_patch.json b/infra/bots/recipe_modules/upload_dm_results/example.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipes/upload_dm_results.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipe_modules/upload_dm_results/example.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipes/upload_dm_results.expected/trybot.json b/infra/bots/recipe_modules/upload_dm_results/example.expected/trybot.json
similarity index 100%
rename from infra/bots/recipes/upload_dm_results.expected/trybot.json
rename to infra/bots/recipe_modules/upload_dm_results/example.expected/trybot.json
diff --git a/infra/bots/recipe_modules/upload_dm_results/example.py b/infra/bots/recipe_modules/upload_dm_results/example.py
new file mode 100644
index 0000000..5444845
--- /dev/null
+++ b/infra/bots/recipe_modules/upload_dm_results/example.py
@@ -0,0 +1,70 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Example recipe w/ coverage.
+
+
+DEPS = [
+  'upload_dm_results',
+  'recipe_engine/properties',
+]
+
+
+def RunSteps(api):
+  api.upload_dm_results.run()
+
+
+def GenTests(api):
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
+  yield (
+    api.test('normal_bot') +
+    api.properties(buildername=builder,
+                   revision='abc123',
+                   path_config='kitchen')
+  )
+
+  yield (
+    api.test('failed_once') +
+    api.properties(buildername=builder,
+                   revision='abc123',
+                   path_config='kitchen') +
+    api.step_data('upload images', retcode=1)
+  )
+
+  yield (
+    api.test('failed_all') +
+    api.properties(buildername=builder,
+                   revision='abc123',
+                   path_config='kitchen') +
+    api.step_data('upload images', retcode=1) +
+    api.step_data('upload images (attempt 2)', retcode=1) +
+    api.step_data('upload images (attempt 3)', retcode=1) +
+    api.step_data('upload images (attempt 4)', retcode=1) +
+    api.step_data('upload images (attempt 5)', retcode=1)
+  )
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
+  yield (
+    api.test('trybot') +
+    api.properties(buildername=builder,
+                   revision='abc123',
+                   path_config='kitchen',
+                   issue='12345',
+                   patchset='1002')
+  )
+
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=builder,
+          revision='abc123',
+          path_config='kitchen',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      )
+  )
diff --git a/infra/bots/recipe_modules/upload_nano_results/__init__.py b/infra/bots/recipe_modules/upload_nano_results/__init__.py
new file mode 100644
index 0000000..eac65b7
--- /dev/null
+++ b/infra/bots/recipe_modules/upload_nano_results/__init__.py
@@ -0,0 +1,11 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+  'build/file',
+  'recipe_engine/path',
+  'recipe_engine/properties',
+  'recipe_engine/step',
+  'recipe_engine/time',
+]
diff --git a/infra/bots/recipe_modules/upload_nano_results/api.py b/infra/bots/recipe_modules/upload_nano_results/api.py
new file mode 100644
index 0000000..515c982
--- /dev/null
+++ b/infra/bots/recipe_modules/upload_nano_results/api.py
@@ -0,0 +1,49 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Recipe for uploading nanobench results.
+
+
+from recipe_engine import recipe_api
+
+
+class UploadNanoResultsApi(recipe_api.RecipeApi):
+  def run(self):
+    # Upload the nanobench resuls.
+    builder_name = self.m.properties['buildername']
+
+    now = self.m.time.utcnow()
+    src_path = self.m.path['start_dir'].join(
+        'perfdata', builder_name, 'data')
+    results = self.m.file.glob(
+        'find results',
+        '*.json',
+        cwd=src_path,
+        test_data=['nanobench_abc123.json'],
+        infra_step=True)
+    if len(results) != 1:  # pragma: nocover
+      raise Exception('Unable to find nanobench or skpbench JSON file!')
+
+    src = src_path.join(results[0])
+    basename = self.m.path.basename(src)
+    gs_path = '/'.join((
+        'nano-json-v1', str(now.year).zfill(4),
+        str(now.month).zfill(2), str(now.day).zfill(2), str(now.hour).zfill(2),
+        builder_name))
+
+    issue = str(self.m.properties.get('issue', ''))
+    patchset = str(self.m.properties.get('patchset', ''))
+    if self.m.properties.get('patch_storage', '') == 'gerrit':
+      issue = str(self.m.properties['patch_issue'])
+      patchset = str(self.m.properties['patch_set'])
+    if issue and patchset:
+      gs_path = '/'.join(('trybot', gs_path, issue, patchset))
+
+    dst = '/'.join(('gs://skia-perf', gs_path, basename))
+
+    self.m.step(
+        'upload',
+        cmd=['gsutil', 'cp', '-a', 'public-read', '-z', 'json', src, dst],
+        infra_step=True)
diff --git a/infra/bots/recipes/upload_nano_results.expected/normal_bot.json b/infra/bots/recipe_modules/upload_nano_results/example.expected/normal_bot.json
similarity index 100%
copy from infra/bots/recipes/upload_nano_results.expected/normal_bot.json
copy to infra/bots/recipe_modules/upload_nano_results/example.expected/normal_bot.json
diff --git a/infra/bots/recipes/upload_nano_results.expected/recipe_with_gerrit_patch.json b/infra/bots/recipe_modules/upload_nano_results/example.expected/recipe_with_gerrit_patch.json
similarity index 100%
rename from infra/bots/recipes/upload_nano_results.expected/recipe_with_gerrit_patch.json
rename to infra/bots/recipe_modules/upload_nano_results/example.expected/recipe_with_gerrit_patch.json
diff --git a/infra/bots/recipes/upload_nano_results.expected/trybot.json b/infra/bots/recipe_modules/upload_nano_results/example.expected/trybot.json
similarity index 100%
rename from infra/bots/recipes/upload_nano_results.expected/trybot.json
rename to infra/bots/recipe_modules/upload_nano_results/example.expected/trybot.json
diff --git a/infra/bots/recipe_modules/upload_nano_results/example.py b/infra/bots/recipe_modules/upload_nano_results/example.py
new file mode 100644
index 0000000..f97cac8
--- /dev/null
+++ b/infra/bots/recipe_modules/upload_nano_results/example.py
@@ -0,0 +1,50 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# Example recipe w/ coverage.
+
+
+DEPS = [
+  'recipe_engine/properties',
+  'upload_nano_results',
+]
+
+
+def RunSteps(api):
+  api.upload_nano_results.run()
+
+
+def GenTests(api):
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
+  yield (
+    api.test('normal_bot') +
+    api.properties(buildername=builder,
+                   revision='abc123',
+                   path_config='kitchen')
+  )
+
+  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
+  yield (
+    api.test('trybot') +
+    api.properties(buildername=builder,
+                   revision='abc123',
+                   path_config='kitchen',
+                   issue='12345',
+                   patchset='1002')
+  )
+
+  yield (
+      api.test('recipe_with_gerrit_patch') +
+      api.properties(
+          buildername=builder,
+          revision='abc123',
+          path_config='kitchen',
+          patch_storage='gerrit') +
+      api.properties.tryserver(
+          buildername=builder,
+          gerrit_project='skia',
+          gerrit_url='https://skia-review.googlesource.com/',
+      )
+  )
diff --git a/infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-Arm7-Release-iOS.json b/infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-Arm7-Release.json
similarity index 100%
copy from infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-Arm7-Release-iOS.json
copy to infra/bots/recipes/swarm_compile.expected/Build-Mac-Clang-Arm7-Release.json
diff --git a/infra/bots/recipes/swarm_compile.py b/infra/bots/recipes/swarm_compile.py
index d888663..f54c1b1 100644
--- a/infra/bots/recipes/swarm_compile.py
+++ b/infra/bots/recipes/swarm_compile.py
@@ -7,234 +7,28 @@
 
 
 DEPS = [
-  'core',
-  'recipe_engine/json',
+  'compile',
   'recipe_engine/path',
   'recipe_engine/platform',
   'recipe_engine/properties',
-  'recipe_engine/python',
-  'flavor',
-  'run',
-  'vars',
 ]
 
 
-TEST_BUILDERS = {
-  'client.skia.compile': {
-    'skiabot-linux-swarm-000': [
-      'Build-Mac-Clang-Arm7-Release-iOS',
-      'Build-Mac-Clang-arm64-Debug-GN_iOS',
-      'Build-Mac-Clang-mipsel-Debug-GN_Android',
-      'Build-Mac-Clang-x86_64-Debug-CommandBuffer',
-      'Build-Mac-Clang-x86_64-Release-GN',
-      'Build-Ubuntu-Clang-arm64-Release-GN_Android',
-      'Build-Ubuntu-Clang-arm64-Release-GN_Android_Vulkan',
-      'Build-Ubuntu-Clang-x86_64-Debug-ASAN',
-      'Build-Ubuntu-Clang-x86_64-Debug-GN',
-      'Build-Ubuntu-Clang-arm64-Debug-GN_Android-Trybot',
-      'Build-Ubuntu-Clang-arm64-Debug-GN_Android_FrameworkDefs',
-      'Build-Ubuntu-GCC-x86-Debug',
-      'Build-Ubuntu-GCC-x86_64-Debug-GN',
-      'Build-Ubuntu-GCC-x86_64-Debug-MSAN',
-      'Build-Ubuntu-GCC-x86_64-Debug-NoGPU',
-      'Build-Ubuntu-GCC-x86_64-Debug-SK_USE_DISCARDABLE_SCALEDIMAGECACHE',
-      'Build-Ubuntu-GCC-x86_64-Release-ANGLE',
-      'Build-Ubuntu-GCC-x86_64-Release-Fast',
-      'Build-Ubuntu-GCC-x86_64-Release-Mesa',
-      'Build-Ubuntu-GCC-x86_64-Release-PDFium',
-      'Build-Ubuntu-GCC-x86_64-Release-PDFium_SkiaPaths',
-      'Build-Ubuntu-GCC-x86_64-Release-Valgrind',
-      'Build-Win-Clang-arm64-Release-GN_Android',
-      'Build-Win-MSVC-x86-Debug',
-      'Build-Win-MSVC-x86-Debug-ANGLE',
-      'Build-Win-MSVC-x86-Debug-Exceptions',
-      'Build-Win-MSVC-x86-Release-GDI',
-      'Build-Win-MSVC-x86-Release-GN',
-      'Build-Win-MSVC-x86_64-Release-Vulkan',
-    ],
-  },
-}
-
-
-def build_targets_from_builder_dict(builder_dict):
-  """Return a list of targets to build, depending on the builder type."""
-  if builder_dict.get('extra_config') == 'iOS':
-    return ['iOSShell']
-  return ['most']
-
-
-def get_extra_env_vars(builder_dict):
-  env = {}
-  if builder_dict.get('compiler') == 'Clang':
-    env['CC'] = '/usr/bin/clang'
-    env['CXX'] = '/usr/bin/clang++'
-
-  # SKNX_NO_SIMD, SK_USE_DISCARDABLE_SCALEDIMAGECACHE, etc.
-  extra_config = builder_dict.get('extra_config', '')
-  if extra_config.startswith('SK') and extra_config.isupper():
-    env['CPPFLAGS'] = '-D' + extra_config
-
-  return env
-
-
-def get_gyp_defines(builder_dict):
-  gyp_defs = {}
-
-  if (builder_dict.get('os') == 'iOS' or
-      builder_dict.get('extra_config') == 'iOS'):
-    gyp_defs['skia_arch_type']  = 'arm'
-    gyp_defs['skia_clang_build'] = '1'
-    gyp_defs['skia_os'] = 'ios'
-    gyp_defs['skia_warnings_as_errors'] = 1
-
-  return gyp_defs
-
-
 def RunSteps(api):
-  api.core.setup()
-
-  env = get_extra_env_vars(api.vars.builder_cfg)
-  gyp_defs = get_gyp_defines(api.vars.builder_cfg)
-  gyp_defs_list = ['%s=%s' % (k, v) for k, v in gyp_defs.iteritems()]
-  gyp_defs_list.sort()
-  env['GYP_DEFINES'] = ' '.join(gyp_defs_list)
-
-  build_targets = build_targets_from_builder_dict(api.vars.builder_cfg)
-
-  try:
-    for target in build_targets:
-      api.flavor.compile(target, env=env)
-    api.run.copy_build_products(
-        api.flavor.out_dir,
-        api.vars.swarming_out_dir.join(
-            'out', api.vars.configuration))
-    api.flavor.copy_extra_build_products(api.vars.swarming_out_dir)
-  finally:
-    if 'Win' in api.vars.builder_cfg.get('os', ''):
-      api.python.inline(
-          name='cleanup',
-          program='''import psutil
-for p in psutil.process_iter():
-  try:
-    if p.name in ('mspdbsrv.exe', 'vctip.exe', 'cl.exe', 'link.exe'):
-      p.kill()
-  except psutil._error.AccessDenied:
-    pass
-''',
-          infra_step=True)
-
-  api.flavor.cleanup_steps()
-  api.run.check_failure()
+  api.compile.run()
 
 
 def GenTests(api):
-  for mastername, slaves in TEST_BUILDERS.iteritems():
-    for slavename, builders_by_slave in slaves.iteritems():
-      for builder in builders_by_slave:
-        test = (
-          api.test(builder) +
-          api.properties(buildername=builder,
-                         mastername=mastername,
-                         slavename=slavename,
-                         buildnumber=5,
-                         revision='abc123',
-                         path_config='kitchen',
-                         swarm_out_dir='[SWARM_OUT_DIR]') +
-          api.path.exists(
-              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-          )
-        )
-        if 'Win' in builder:
-          test += api.platform('win', 64)
-        elif 'Mac' in builder:
-          test += api.platform('mac', 64)
-        else:
-          test += api.platform('linux', 64)
-        if 'Trybot' in builder:
-          test += api.properties(issue=500,
-                                 patchset=1,
-                                 rietveld='https://codereview.chromium.org')
-
-        yield test
-
-  mastername = 'client.skia.compile'
-  slavename = 'skiabot-win-compile-000'
-  buildername = 'Build-Win-MSVC-x86-Debug'
   yield (
-      api.test('big_issue_number') +
-      api.properties(buildername=buildername,
-                     mastername=mastername,
-                     slavename=slavename,
-                     buildnumber=5,
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]',
-                     rietveld='https://codereview.chromium.org',
-                     patchset=1,
-                     issue=2147533002L) +
-      api.path.exists(
-          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-      ) +
-      api.platform('win', 64)
-  )
-
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=buildername + '-Trybot',
-          mastername=mastername,
-          slavename=slavename,
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=buildername + '-Trybot',
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.platform('win', 64)
-  )
-
-  yield (
-      api.test('buildbotless_trybot_rietveld') +
-      api.properties(
-          repository='skia',
-          buildername=buildername,
-          mastername=mastername,
-          slavename=slavename,
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          nobuildbot='True',
-          issue=500,
-          patchset=1,
-          patch_storage='rietveld',
-          rietveld='https://codereview.chromium.org') +
-      api.platform('win', 64)
-  )
-
-  yield (
-      api.test('buildbotless_trybot_gerrit') +
-      api.properties(
-          repository='skia',
-          buildername=buildername,
-          mastername=mastername,
-          slavename=slavename,
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          nobuildbot='True',
-          patch_issue=500,
-          patch_set=1,
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=buildername,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.platform('win', 64)
+    api.test('Build-Mac-Clang-Arm7-Release') +
+    api.properties(buildername='Build-Mac-Clang-Arm7-Release-iOS',
+                   mastername='fake-master',
+                   slavename='fake-slave',
+                   buildnumber=5,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
+    api.path.exists(
+        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
+    )
   )
diff --git a/infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release.json b/infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release.json
new file mode 100644
index 0000000..64bc9ad
--- /dev/null
+++ b/infra/bots/recipes/swarm_perf.expected/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release.json
@@ -0,0 +1,206 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SKP VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "name": "Get downloaded SVG VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport shutil\nimport sys\nshutil.copy(sys.argv[1], sys.argv[2])\n",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport os, sys\nfrom common import chromium_utils # Error? See https://crbug.com/584783.\n\n\nif os.path.exists(sys.argv[1]):\n  chromium_utils.RemoveDirectory(sys.argv[1])\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release/data"
+    ],
+    "env": {
+      "PYTHONPATH": "[START_DIR]/skia/infra/bots/.recipe_deps/build/scripts"
+    },
+    "name": "rmtree data",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os, sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@from common import chromium_utils # Error? See https://crbug.com/584783.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@if os.path.exists(sys.argv[1]):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  chromium_utils.RemoveDirectory(sys.argv[1])@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release/data",
+      "511"
+    ],
+    "name": "makedirs data",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "catchsegv",
+      "[START_DIR]/out/Release/nanobench",
+      "--undefok",
+      "-i",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/nanobench",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--nogpu",
+      "--pre_log",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--config",
+      "8888",
+      "gpu",
+      "nonrendering",
+      "hwui",
+      "f16",
+      "srgb",
+      "565",
+      "msaa16",
+      "nvpr16",
+      "nvprdit16",
+      "--match",
+      "~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",
+      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release/data/nanobench_abc123_1337000001.json",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "build_number",
+      "5",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "cpu_or_gpu",
+      "CPU",
+      "cpu_or_gpu_value",
+      "AVX2",
+      "model",
+      "GCE",
+      "os",
+      "Ubuntu"
+    ],
+    "cwd": "[START_DIR]/skia",
+    "name": "nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport sys, os\npath = sys.argv[1]\nmode = int(sys.argv[2])\nif not os.path.isdir(path):\n  if os.path.exists(path):\n    print \"%s exists but is not a dir\" % path\n    sys.exit(1)\n  os.makedirs(path, mode)\n",
+      "[CUSTOM_[SWARM_OUT_DIR]]/perfdata/Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release/data",
+      "511"
+    ],
+    "name": "makedirs perf_dir",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys, os@@@",
+      "@@@STEP_LOG_LINE@python.inline@path = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@mode = int(sys.argv[2])@@@",
+      "@@@STEP_LOG_LINE@python.inline@if not os.path.isdir(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if os.path.exists(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print \"%s exists but is not a dir\" % path@@@",
+      "@@@STEP_LOG_LINE@python.inline@    sys.exit(1)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(path, mode)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/swarm_perf.py b/infra/bots/recipes/swarm_perf.py
index 42123c9..4246a69 100644
--- a/infra/bots/recipes/swarm_perf.py
+++ b/infra/bots/recipes/swarm_perf.py
@@ -7,359 +7,34 @@
 
 
 DEPS = [
-  'build/file',
-  'core',
-  'recipe_engine/json',
+  'perf',
   'recipe_engine/path',
   'recipe_engine/platform',
   'recipe_engine/properties',
   'recipe_engine/raw_io',
-  'recipe_engine/time',
-  'run',
-  'flavor',
-  'vars',
 ]
 
 
-TEST_BUILDERS = {
-  'client.skia': {
-    'skiabot-linux-swarm-000': [
-      ('Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug' +
-       '-GN_Android_Vulkan'),
-      'Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-GN_Android',
-      'Perf-Android-Clang-Nexus6-GPU-Adreno420-arm-Release-GN_Android',
-      'Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-GN_Android',
-      'Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-GN_Android',
-      'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android',
-      'Perf-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Release-GN',
-      'Perf-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer',
-      'Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-GN',
-      'Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind',
-      'Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-ANGLE',
-      'Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Debug',
-      'Perf-Win-MSVC-GCE-CPU-AVX2-x86_64-Release',
-      'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot',
-      'Perf-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE',
-      'Perf-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug',
-    ],
-  },
-}
-
-
-import calendar
-
-
-def nanobench_flags(bot):
-  args = ['--pre_log']
-
-  if 'GPU' in bot:
-    args.append('--images')
-    args.extend(['--gpuStatsDump', 'true'])
-
-  if 'Android' in bot and 'GPU' in bot:
-    args.extend(['--useThermalManager', '1,1,10,1000'])
-
-  args.extend(['--scales', '1.0', '1.1'])
-
-  if 'iOS' in bot:
-    args.extend(['--skps', 'ignore_skps'])
-
-  config = ['8888', 'gpu', 'nonrendering', 'hwui' ]
-  if 'AndroidOne' not in bot:
-    config += [ 'f16', 'srgb' ]
-  if '-GCE-' in bot:
-    config += [ '565' ]
-  # The NP produces a long error stream when we run with MSAA.
-  if 'NexusPlayer' not in bot:
-    if 'Android' in bot:
-      # The NVIDIA_Shield has a regular OpenGL implementation. We bench that
-      # instead of ES.
-      if 'NVIDIA_Shield' in bot:
-        config.remove('gpu')
-        config.extend(['gl', 'glmsaa4', 'glnvpr4', 'glnvprdit4'])
-      else:
-        config.extend(['msaa4', 'nvpr4', 'nvprdit4'])
-    else:
-      config.extend(['msaa16', 'nvpr16', 'nvprdit16'])
-
-  # Bench instanced rendering on a limited number of platforms
-  if 'Nexus6' in bot:
-    config.append('esinst') # esinst4 isn't working yet on Adreno.
-  elif 'PixelC' in bot:
-    config.extend(['esinst', 'esinst4'])
-  elif 'NVIDIA_Shield' in bot:
-    config.extend(['glinst', 'glinst4'])
-  elif 'MacMini6.2' in bot:
-    config.extend(['glinst', 'glinst16'])
-
-  if 'CommandBuffer' in bot:
-    config = ['commandbuffer']
-  if 'Vulkan' in bot:
-    config = ['vk']
-
-  if 'ANGLE' in bot:
-    config.extend(['angle_d3d11_es2'])
-    # The GL backend of ANGLE crashes on the perf bot currently.
-    if 'Win' not in bot:
-      config.extend(['angle_gl_es2'])
-
-  args.append('--config')
-  args.extend(config)
-
-  if 'Valgrind' in bot:
-    # Don't care about Valgrind performance.
-    args.extend(['--loops',   '1'])
-    args.extend(['--samples', '1'])
-    # Ensure that the bot framework does not think we have timed out.
-    args.extend(['--keepAlive', 'true'])
-
-  match = []
-  if 'Android' in bot:
-    # Segfaults when run as GPU bench. Very large texture?
-    match.append('~blurroundrect')
-    match.append('~patch_grid')  # skia:2847
-    match.append('~desk_carsvg')
-  if 'NexusPlayer' in bot:
-    match.append('~desk_unicodetable')
-  if 'Nexus5' in bot:
-    match.append('~keymobi_shop_mobileweb_ebay_com.skp')  # skia:5178
-  if 'iOS' in bot:
-    match.append('~blurroundrect')
-    match.append('~patch_grid')  # skia:2847
-    match.append('~desk_carsvg')
-    match.append('~keymobi')
-    match.append('~path_hairline')
-    match.append('~GLInstancedArraysBench') # skia:4714
-
-  # We do not need or want to benchmark the decodes of incomplete images.
-  # In fact, in nanobench we assert that the full image decode succeeds.
-  match.append('~inc0.gif')
-  match.append('~inc1.gif')
-  match.append('~incInterlaced.gif')
-  match.append('~inc0.jpg')
-  match.append('~incGray.jpg')
-  match.append('~inc0.wbmp')
-  match.append('~inc1.wbmp')
-  match.append('~inc0.webp')
-  match.append('~inc1.webp')
-  match.append('~inc0.ico')
-  match.append('~inc1.ico')
-  match.append('~inc0.png')
-  match.append('~inc1.png')
-  match.append('~inc2.png')
-  match.append('~inc12.png')
-  match.append('~inc13.png')
-  match.append('~inc14.png')
-  match.append('~inc0.webp')
-  match.append('~inc1.webp')
-
-  if match:
-    args.append('--match')
-    args.extend(match)
-
-  return args
-
-
-def perf_steps(api):
-  """Run Skia benchmarks."""
-  if api.vars.upload_perf_results:
-    api.flavor.create_clean_device_dir(
-        api.flavor.device_dirs.perf_data_dir)
-
-  # Run nanobench.
-  properties = [
-    '--properties',
-    'gitHash',      api.vars.got_revision,
-    'build_number', api.vars.build_number,
-  ]
-  if api.vars.is_trybot:
-    properties.extend([
-      'issue',    api.vars.issue,
-      'patchset', api.vars.patchset,
-      'patch_storage', api.vars.patch_storage,
-    ])
-  if api.vars.no_buildbot:
-    properties.extend(['no_buildbot', 'True'])
-    properties.extend(['swarming_bot_id', api.vars.swarming_bot_id])
-    properties.extend(['swarming_task_id', api.vars.swarming_task_id])
-
-  target = 'nanobench'
-  args = [
-      target,
-      '--undefok',   # This helps branches that may not know new flags.
-      '-i',       api.flavor.device_dirs.resource_dir,
-      '--skps',   api.flavor.device_dirs.skp_dir,
-      '--images', api.flavor.device_path_join(
-          api.flavor.device_dirs.images_dir, 'nanobench'),
-  ]
-
-  # Do not run svgs on Valgrind.
-  if 'Valgrind' not in api.vars.builder_name:
-    args.extend(['--svgs',  api.flavor.device_dirs.svg_dir])
-
-  skip_flag = None
-  if api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
-    skip_flag = '--nogpu'
-  elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
-    skip_flag = '--nocpu'
-  if skip_flag:
-    args.append(skip_flag)
-  args.extend(nanobench_flags(api.vars.builder_name))
-
-  if api.vars.upload_perf_results:
-    now = api.time.utcnow()
-    ts = int(calendar.timegm(now.utctimetuple()))
-    json_path = api.flavor.device_path_join(
-        api.flavor.device_dirs.perf_data_dir,
-        'nanobench_%s_%d.json' % (api.vars.got_revision, ts))
-    args.extend(['--outResultsFile', json_path])
-    args.extend(properties)
-
-    keys_blacklist = ['configuration', 'role', 'is_trybot']
-    args.append('--key')
-    for k in sorted(api.vars.builder_cfg.keys()):
-      if not k in keys_blacklist:
-        args.extend([k, api.vars.builder_cfg[k]])
-
-  api.run(api.flavor.step, target, cmd=args,
-          abort_on_failure=False,
-          env=api.vars.default_env)
-
-  # See skia:2789.
-  if ('Valgrind' in api.vars.builder_name and
-      api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU'):
-    abandonGpuContext = list(args)
-    abandonGpuContext.extend(['--abandonGpuContext', '--nocpu'])
-    api.run(api.flavor.step,
-            '%s --abandonGpuContext' % target,
-            cmd=abandonGpuContext, abort_on_failure=False,
-            env=api.vars.default_env)
-
-  # Copy results to swarming out dir.
-  if api.vars.upload_perf_results:
-    api.file.makedirs('perf_dir', api.vars.perf_data_dir)
-    api.flavor.copy_directory_contents_to_host(
-        api.flavor.device_dirs.perf_data_dir,
-        api.vars.perf_data_dir)
-
-
 def RunSteps(api):
-  api.core.setup()
-  try:
-    api.flavor.install_everything()
-    perf_steps(api)
-  finally:
-    api.flavor.cleanup_steps()
-  api.run.check_failure()
+  api.perf.run()
 
 
 def GenTests(api):
-  for mastername, slaves in TEST_BUILDERS.iteritems():
-    for slavename, builders_by_slave in slaves.iteritems():
-      for builder in builders_by_slave:
-        test = (
-          api.test(builder) +
-          api.properties(buildername=builder,
-                         mastername=mastername,
-                         slavename=slavename,
-                         buildnumber=5,
-                         revision='abc123',
-                         path_config='kitchen',
-                         swarm_out_dir='[SWARM_OUT_DIR]') +
-          api.path.exists(
-              api.path['start_dir'].join('skia'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skimage', 'VERSION'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skp', 'VERSION'),
-              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-          )
-        )
-        if 'Trybot' in builder:
-          test += api.properties(issue=500,
-                                 patchset=1,
-                                 rietveld='https://codereview.chromium.org')
-        if 'Win' in builder:
-          test += api.platform('win', 64)
-
-        yield test
-
-  builder = 'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot'
   yield (
-    api.test('big_issue_number') +
-    api.properties(buildername=builder,
-                   mastername='client.skia.compile',
-                   slavename='skiabot-linux-swarm-000',
+    api.test('Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release') +
+    api.properties(buildername='Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release',
+                   mastername='fake-master',
+                   slavename='fake-slave',
                    buildnumber=5,
                    revision='abc123',
                    path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]',
-                   rietveld='https://codereview.chromium.org',
-                   patchset=1,
-                   issue=2147533002L) +
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
+                                   'skimage', 'VERSION'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
+                                   'skp', 'VERSION'),
         api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.platform('win', 64)
-  )
-
-  builder = ('Perf-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind-'
-             'Trybot')
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=builder,
-          mastername='client.skia',
-          slavename='skiabot-linux-swarm-000',
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      )
-  )
-
-  builder = 'Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release-Trybot'
-  yield (
-      api.test('nobuildbot') +
-      api.properties(
-          buildername=builder,
-          mastername='client.skia',
-          slavename='skiabot-linux-swarm-000',
-          buildnumber=5,
-          revision='abc123',
-          path_config='kitchen',
-          nobuildbot='True',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.path.exists(
-          api.path['start_dir'].join('skia'),
-          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                       'skimage', 'VERSION'),
-          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                       'skp', 'VERSION'),
-          api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                       'svg', 'VERSION'),
-          api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-      ) +
-      api.platform('win', 64) +
-      api.step_data('get swarming bot id',
-          stdout=api.raw_io.output('skia-bot-123')) +
-      api.step_data('get swarming task id', stdout=api.raw_io.output('123456'))
+    )
   )
diff --git a/infra/bots/recipes/swarm_skpbench.py b/infra/bots/recipes/swarm_skpbench.py
index 7cd96f6..4561f61 100644
--- a/infra/bots/recipes/swarm_skpbench.py
+++ b/infra/bots/recipes/swarm_skpbench.py
@@ -7,134 +7,35 @@
 
 
 DEPS = [
-  'build/file',
-  'core',
   'recipe_engine/path',
   'recipe_engine/properties',
-  'recipe_engine/python',
   'recipe_engine/raw_io',
-  'recipe_engine/step',
-  'recipe_engine/time',
-  'run',
-  'flavor',
-  'vars',
+  'skpbench',
 ]
 
 
-TEST_BUILDERS = {
-  'client.skia': {
-    'skiabot-linux-swarm-000': [
-      'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench',
-      ('Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-' +
-      'GN_Android_Vulkan_Skpbench'),
-    ],
-  },
-}
-
-
-import calendar
-
-
-def _run(api, title, *cmd, **kwargs):
-  return api.run(api.step, title, cmd=list(cmd),
-                 cwd=api.vars.skia_dir, **kwargs)
-
-
-def _adb(api, title, *cmd, **kwargs):
-  if 'infra_step' not in kwargs:
-    kwargs['infra_step'] = True
-  return _run(api, title, 'adb', *cmd, **kwargs)
-
-
-def skpbench_steps(api):
-  """benchmark Skia using skpbench."""
-  app = api.vars.skia_out.join(api.vars.configuration, 'skpbench')
-  _adb(api, 'push skpbench', 'push', app, api.vars.android_bin_dir)
-
-  skpbench_dir = api.vars.slave_dir.join('skia', 'tools', 'skpbench')
-  table = api.path.join(api.vars.swarming_out_dir, 'table')
-
-  config = 'gpu,esinst4'
-  if 'Vulkan' in api.vars.builder_name:
-    config = 'vk'
-
-  skpbench_args = [
-        api.path.join(api.vars.android_bin_dir, 'skpbench'),
-        api.path.join(api.vars.android_data_dir, 'skps'),
-        '--adb',
-        '--resultsfile', table,
-        '--config', config]
-
-  api.run(api.python, 'skpbench',
-      script=skpbench_dir.join('skpbench.py'),
-      args=skpbench_args)
-
-  skiaperf_args = [
-    table,
-    '--properties',
-    'gitHash',      api.vars.got_revision,
-    'build_number', api.vars.build_number,
-  ]
-
-  skiaperf_args.extend(['no_buildbot', 'True'])
-  skiaperf_args.extend(['swarming_bot_id', api.vars.swarming_bot_id])
-  skiaperf_args.extend(['swarming_task_id', api.vars.swarming_task_id])
-
-  now = api.time.utcnow()
-  ts = int(calendar.timegm(now.utctimetuple()))
-  api.file.makedirs('perf_dir', api.vars.perf_data_dir)
-  json_path = api.path.join(
-      api.vars.perf_data_dir,
-      'skpbench_%s_%d.json' % (api.vars.got_revision, ts))
-
-  skiaperf_args.extend([
-    '--outfile', json_path
-  ])
-
-  keys_blacklist = ['configuration', 'role', 'is_trybot']
-  skiaperf_args.append('--key')
-  for k in sorted(api.vars.builder_cfg.keys()):
-    if not k in keys_blacklist:
-      skiaperf_args.extend([k, api.vars.builder_cfg[k]])
-
-  api.run(api.python, 'Parse skpbench output into Perf json',
-      script=skpbench_dir.join('skiaperf.py'),
-      args=skiaperf_args)
-
-
 def RunSteps(api):
-  api.core.setup()
-  try:
-    api.flavor.install(skps=True)
-    skpbench_steps(api)
-  finally:
-    api.flavor.cleanup_steps()
-  api.run.check_failure()
+  api.skpbench.run()
 
 
 def GenTests(api):
-  for mastername, slaves in TEST_BUILDERS.iteritems():
-    for slavename, builders_by_slave in slaves.iteritems():
-      for builder in builders_by_slave:
-        test = (
-          api.test(builder) +
-          api.properties(buildername=builder,
-                         mastername=mastername,
-                         slavename=slavename,
-                         buildnumber=5,
-                         revision='abc123',
-                         path_config='kitchen',
-                         swarm_out_dir='[SWARM_OUT_DIR]') +
-          api.path.exists(
-              api.path['start_dir'].join('skia'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skp', 'VERSION'),
-          ) +
-          api.step_data('get swarming bot id',
-              stdout=api.raw_io.output('skia-bot-123')) +
-          api.step_data('get swarming task id',
-              stdout=api.raw_io.output('123456'))
-        )
-
-        yield test
-
+  b = 'Perf-Android-Clang-PixelC-GPU-TegraX1-arm64-Release-GN_Android_Skpbench'
+  yield (
+    api.test(b) +
+    api.properties(buildername=b,
+                   mastername='fake-master',
+                   slavename='fake-slave',
+                   buildnumber=5,
+                   revision='abc123',
+                   path_config='kitchen',
+                   swarm_out_dir='[SWARM_OUT_DIR]') +
+    api.path.exists(
+        api.path['start_dir'].join('skia'),
+        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
+                                   'skp', 'VERSION'),
+    ) +
+    api.step_data('get swarming bot id',
+        stdout=api.raw_io.output('skia-bot-123')) +
+    api.step_data('get swarming task id',
+        stdout=api.raw_io.output('123456'))
+  )
diff --git a/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json b/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
index 0a14eb6..29decb9 100644
--- a/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
+++ b/infra/bots/recipes/swarm_test.expected/Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug.json
@@ -191,7 +191,7 @@
       "gitHash",
       "abc123",
       "master",
-      "client.skia",
+      "fake-master",
       "builder",
       "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug",
       "build_number",
diff --git a/infra/bots/recipes/swarm_test.py b/infra/bots/recipes/swarm_test.py
index b4d77ba..2e101ae 100644
--- a/infra/bots/recipes/swarm_test.py
+++ b/infra/bots/recipes/swarm_test.py
@@ -7,724 +7,36 @@
 
 
 DEPS = [
-  'build/file',
-  'core',
-  'recipe_engine/json',
   'recipe_engine/path',
   'recipe_engine/platform',
   'recipe_engine/properties',
-  'recipe_engine/python',
   'recipe_engine/raw_io',
-  'flavor',
-  'run',
-  'vars',
+  'sktest',
 ]
 
 
-TEST_BUILDERS = {
-  'client.skia': {
-    'skiabot-linux-swarm-000': [
-      'Test-Android-Clang-AndroidOne-CPU-MT6582-arm-Release-GN_Android',
-      'Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-GN_Android',
-      'Test-Android-Clang-GalaxyS7-GPU-Adreno530-arm64-Debug-GN_Android',
-      'Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-GN_Android',
-      'Test-Android-Clang-Nexus10-GPU-MaliT604-arm-Release-GN_Android',
-      'Test-Android-Clang-Nexus6-GPU-Adreno420-arm-Debug-GN_Android',
-      'Test-Android-Clang-Nexus6p-GPU-Adreno430-arm64-Debug-GN_Android_Vulkan',
-      'Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android',
-      'Test-Android-Clang-NexusPlayer-CPU-SSE4-x86-Release-GN_Android',
-      'Test-Android-Clang-PixelC-GPU-TegraX1-arm64-Debug-GN_Android',
-      'Test-Mac-Clang-MacMini4.1-GPU-GeForce320M-x86_64-Debug',
-      'Test-Mac-Clang-MacMini6.2-CPU-AVX-x86_64-Debug',
-      'Test-Mac-Clang-MacMini6.2-GPU-HD4000-x86_64-Debug-CommandBuffer',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-ASAN',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-MSAN',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Shared',
-      'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-TSAN',
-      'Test-Ubuntu-GCC-ShuttleA-GPU-GTX550Ti-x86_64-Release-Valgrind',
-      'Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug-Vulkan',
-      'Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot',
-      'Test-Win8-MSVC-ShuttleB-GPU-GTX960-x86_64-Debug-ANGLE',
-      'Test-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug',
-    ],
-  },
-}
-
-
-def dm_flags(bot):
-  args = []
-
-  # 32-bit desktop bots tend to run out of memory, because they have relatively
-  # far more cores than RAM (e.g. 32 cores, 3G RAM).  Hold them back a bit.
-  if '-x86-' in bot and not 'NexusPlayer' in bot:
-    args.extend('--threads 4'.split(' '))
-
-  # These are the canonical configs that we would ideally run on all bots. We
-  # may opt out or substitute some below for specific bots
-  configs = ['8888', 'srgb', 'gpu', 'gpudft', 'gpusrgb', 'pdf']
-  # Add in either msaa4 or msaa16 to the canonical set of configs to run
-  if 'Android' in bot or 'iOS' in bot:
-    configs.append('msaa4')
-  else:
-    configs.append('msaa16')
-
-  # The NP produces a long error stream when we run with MSAA. The Tegra3 just
-  # doesn't support it.
-  if ('NexusPlayer' in bot or
-      'Tegra3'      in bot or
-      # We aren't interested in fixing msaa bugs on iPad4.
-      'iPad4'       in bot or
-      # skia:5792
-      'iHD530'      in bot):
-    configs = [x for x in configs if 'msaa' not in x]
-
-  # The NP produces different images for dft on every run.
-  if 'NexusPlayer' in bot:
-    configs = [x for x in configs if 'gpudft' not in x]
-
-  # Runs out of memory on Android bots.  Everyone else seems fine.
-  if 'Android' in bot:
-    configs.remove('pdf')
-
-  if '-GCE-' in bot:
-    configs.extend(['565'])
-    configs.extend(['f16'])
-    configs.extend(['sp-8888', '2ndpic-8888'])   # Test niche uses of SkPicture.
-    configs.extend(['lite-8888'])                # Experimental display list.
-
-  if '-TSAN' not in bot:
-    if ('TegraK1'  in bot or
-        'TegraX1'  in bot or
-        'GTX550Ti' in bot or
-        'GTX660'   in bot or
-        'GT610'    in bot):
-      if 'Android' in bot:
-        configs.append('nvprdit4')
-      else:
-        configs.append('nvprdit16')
-
-  # We want to test the OpenGL config not the GLES config on the Shield
-  if 'NVIDIA_Shield' in bot:
-    configs = [x.replace('gpu', 'gl') for x in configs]
-    configs = [x.replace('msaa', 'glmsaa') for x in configs]
-    configs = [x.replace('nvpr', 'glnvpr') for x in configs]
-
-  # NP is running out of RAM when we run all these modes.  skia:3255
-  if 'NexusPlayer' not in bot:
-    configs.extend(mode + '-8888' for mode in
-                   ['serialize', 'tiles_rt', 'pic'])
-
-  # Test instanced rendering on a limited number of platforms
-  if 'Nexus6' in bot:
-    configs.append('esinst') # esinst4 isn't working yet on Adreno.
-  elif 'NVIDIA_Shield' in bot:
-    # Multisampled instanced configs use nvpr.
-    configs = [x.replace('glnvpr', 'glinst') for x in configs]
-    configs.append('glinst')
-  elif 'PixelC' in bot:
-    # Multisampled instanced configs use nvpr.
-    configs = [x.replace('nvpr', 'esinst') for x in configs]
-    configs.append('esinst')
-  elif 'MacMini6.2' in bot:
-    configs.extend(['glinst', 'glinst16'])
-
-  # CommandBuffer bot *only* runs the command_buffer config.
-  if 'CommandBuffer' in bot:
-    configs = ['commandbuffer']
-
-  # ANGLE bot *only* runs the angle configs
-  if 'ANGLE' in bot:
-    configs = ['angle_d3d11_es2',
-               'angle_d3d9_es2',
-               'angle_d3d11_es2_msaa4',
-               'angle_gl_es2']
-
-  # Vulkan bot *only* runs the vk config.
-  if 'Vulkan' in bot:
-    configs = ['vk']
-
-  args.append('--config')
-  args.extend(configs)
-
-  # Run tests, gms, and image decoding tests everywhere.
-  args.extend('--src tests gm image colorImage svg'.split(' '))
-
-  if 'GalaxyS' in bot:
-    args.extend(('--threads', '0'))
-
-  blacklisted = []
-  def blacklist(quad):
-    config, src, options, name = quad.split(' ') if type(quad) is str else quad
-    if config == '_' or config in configs:
-      blacklisted.extend([config, src, options, name])
-
-  # TODO: ???
-  blacklist('f16 _ _ dstreadshuffle')
-  blacklist('f16 image _ _')
-  blacklist('gpusrgb image _ _')
-  blacklist('glsrgb image _ _')
-
-  # Decoder tests are now performing gamma correct decodes.  This means
-  # that, when viewing the results, we need to perform a gamma correct
-  # encode to PNG.  Therefore, we run the image tests in srgb mode instead
-  # of 8888.
-  blacklist('8888 image _ _')
-
-  if 'Valgrind' in bot:
-    # These take 18+ hours to run.
-    blacklist('pdf gm _ fontmgr_iter')
-    blacklist('pdf _ _ PANO_20121023_214540.jpg')
-    blacklist('pdf skp _ worldjournal')
-    blacklist('pdf skp _ desk_baidu.skp')
-    blacklist('pdf skp _ desk_wikipedia.skp')
-    blacklist('_ svg _ _')
-
-  if 'iOS' in bot:
-    blacklist('gpu skp _ _')
-    blacklist('msaa skp _ _')
-    blacklist('msaa16 gm _ tilemodesProcess')
-
-  if 'Mac' in bot or 'iOS' in bot:
-    # CG fails on questionable bmps
-    blacklist('_ image gen_platf rgba32abf.bmp')
-    blacklist('_ image gen_platf rgb24prof.bmp')
-    blacklist('_ image gen_platf rgb24lprof.bmp')
-    blacklist('_ image gen_platf 8bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 4bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 32bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 24bpp-pixeldata-cropped.bmp')
-
-    # CG has unpredictable behavior on this questionable gif
-    # It's probably using uninitialized memory
-    blacklist('_ image gen_platf frame_larger_than_image.gif')
-
-    # CG has unpredictable behavior on incomplete pngs
-    # skbug.com/5774
-    blacklist('_ image gen_platf inc0.png')
-    blacklist('_ image gen_platf inc1.png')
-    blacklist('_ image gen_platf inc2.png')
-    blacklist('_ image gen_platf inc3.png')
-    blacklist('_ image gen_platf inc4.png')
-    blacklist('_ image gen_platf inc5.png')
-    blacklist('_ image gen_platf inc6.png')
-    blacklist('_ image gen_platf inc7.png')
-    blacklist('_ image gen_platf inc8.png')
-    blacklist('_ image gen_platf inc9.png')
-    blacklist('_ image gen_platf inc10.png')
-    blacklist('_ image gen_platf inc11.png')
-    blacklist('_ image gen_platf inc12.png')
-    blacklist('_ image gen_platf inc13.png')
-    blacklist('_ image gen_platf inc14.png')
-
-  # WIC fails on questionable bmps
-  if 'Win' in bot:
-    blacklist('_ image gen_platf rle8-height-negative.bmp')
-    blacklist('_ image gen_platf rle4-height-negative.bmp')
-    blacklist('_ image gen_platf pal8os2v2.bmp')
-    blacklist('_ image gen_platf pal8os2v2-16.bmp')
-    blacklist('_ image gen_platf rgba32abf.bmp')
-    blacklist('_ image gen_platf rgb24prof.bmp')
-    blacklist('_ image gen_platf rgb24lprof.bmp')
-    blacklist('_ image gen_platf 8bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 4bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 32bpp-pixeldata-cropped.bmp')
-    blacklist('_ image gen_platf 24bpp-pixeldata-cropped.bmp')
-    if 'x86_64' in bot and 'CPU' in bot:
-      # This GM triggers a SkSmallAllocator assert.
-      blacklist('_ gm _ composeshader_bitmap')
-
-  if 'Android' in bot or 'iOS' in bot:
-    # This test crashes the N9 (perhaps because of large malloc/frees). It also
-    # is fairly slow and not platform-specific. So we just disable it on all of
-    # Android and iOS. skia:5438
-    blacklist('_ test _ GrShape')
-
-  if 'Win8' in bot:
-    # bungeman: "Doesn't work on Windows anyway, produces unstable GMs with
-    # 'Unexpected error' from DirectWrite"
-    blacklist('_ gm _ fontscalerdistortable')
-    # skia:5636
-    blacklist('_ svg _ Nebraska-StateSeal.svg')
-
-  # skia:4095
-  bad_serialize_gms = ['bleed_image',
-                       'c_gms',
-                       'colortype',
-                       'colortype_xfermodes',
-                       'drawfilter',
-                       'fontmgr_bounds_0.75_0',
-                       'fontmgr_bounds_1_-0.25',
-                       'fontmgr_bounds',
-                       'fontmgr_match',
-                       'fontmgr_iter',
-                       'imagemasksubset']
-
-  # skia:5589
-  bad_serialize_gms.extend(['bitmapfilters',
-                            'bitmapshaders',
-                            'bleed',
-                            'bleed_alpha_bmp',
-                            'bleed_alpha_bmp_shader',
-                            'convex_poly_clip',
-                            'extractalpha',
-                            'filterbitmap_checkerboard_32_32_g8',
-                            'filterbitmap_image_mandrill_64',
-                            'shadows',
-                            'simpleaaclip_aaclip'])
-  # skia:5595
-  bad_serialize_gms.extend(['composeshader_bitmap',
-                            'scaled_tilemodes_npot',
-                            'scaled_tilemodes'])
-
-  # skia:5778
-  bad_serialize_gms.append('typefacerendering_pfaMac')
-  # skia:5942
-  bad_serialize_gms.append('parsedpaths')
-
-  # these use a custom image generator which doesn't serialize
-  bad_serialize_gms.append('ImageGeneratorExternal_rect')
-  bad_serialize_gms.append('ImageGeneratorExternal_shader')
-
-  for test in bad_serialize_gms:
-    blacklist(['serialize-8888', 'gm', '_', test])
-
-  if 'Mac' not in bot:
-    for test in ['bleed_alpha_image', 'bleed_alpha_image_shader']:
-      blacklist(['serialize-8888', 'gm', '_', test])
-  # It looks like we skip these only for out-of-memory concerns.
-  if 'Win' in bot or 'Android' in bot:
-    for test in ['verylargebitmap', 'verylarge_picture_image']:
-      blacklist(['serialize-8888', 'gm', '_', test])
-
-  # skia:4769
-  for test in ['drawfilter']:
-    blacklist([    'sp-8888', 'gm', '_', test])
-    blacklist([   'pic-8888', 'gm', '_', test])
-    blacklist(['2ndpic-8888', 'gm', '_', test])
-    blacklist([  'lite-8888', 'gm', '_', test])
-  # skia:4703
-  for test in ['image-cacherator-from-picture',
-               'image-cacherator-from-raster',
-               'image-cacherator-from-ctable']:
-    blacklist([       'sp-8888', 'gm', '_', test])
-    blacklist([      'pic-8888', 'gm', '_', test])
-    blacklist([   '2ndpic-8888', 'gm', '_', test])
-    blacklist(['serialize-8888', 'gm', '_', test])
-
-  # GM that requires raster-backed canvas
-  for test in ['gamut', 'complexclip4_bw', 'complexclip4_aa']:
-    blacklist([       'sp-8888', 'gm', '_', test])
-    blacklist([      'pic-8888', 'gm', '_', test])
-    blacklist([     'lite-8888', 'gm', '_', test])
-    blacklist([   '2ndpic-8888', 'gm', '_', test])
-    blacklist(['serialize-8888', 'gm', '_', test])
-
-  # GM that not support tiles_rt
-  for test in ['complexclip4_bw', 'complexclip4_aa']:
-    blacklist([ 'tiles_rt-8888', 'gm', '_', test])
-
-  # Extensions for RAW images
-  r = ["arw", "cr2", "dng", "nef", "nrw", "orf", "raf", "rw2", "pef", "srw",
-       "ARW", "CR2", "DNG", "NEF", "NRW", "ORF", "RAF", "RW2", "PEF", "SRW"]
-
-  # skbug.com/4888
-  # Blacklist RAW images (and a few large PNGs) on GPU bots
-  # until we can resolve failures
-  if 'GPU' in bot:
-    blacklist('_ image _ interlaced1.png')
-    blacklist('_ image _ interlaced2.png')
-    blacklist('_ image _ interlaced3.png')
-    for raw_ext in r:
-      blacklist('_ image _ .%s' % raw_ext)
-
-  # Large image that overwhelms older Mac bots
-  if 'MacMini4.1-GPU' in bot:
-    blacklist('_ image _ abnormal.wbmp')
-    blacklist(['msaa16', 'gm', '_', 'blurcircles'])
-
-  match = []
-  if 'Valgrind' in bot: # skia:3021
-    match.append('~Threaded')
-
-  if 'AndroidOne' in bot:  # skia:4711
-    match.append('~WritePixels')
-
-  if 'NexusPlayer' in bot:
-    match.append('~ResourceCache')
-
-  if 'Nexus10' in bot: # skia:5509
-    match.append('~CopySurface')
-
-  if 'ANGLE' in bot and 'Debug' in bot:
-    match.append('~GLPrograms') # skia:4717
-
-  if 'MSAN' in bot:
-    match.extend(['~Once', '~Shared'])  # Not sure what's up with these tests.
-
-  if 'TSAN' in bot:
-    match.extend(['~ReadWriteAlpha'])   # Flaky on TSAN-covered on nvidia bots.
-    match.extend(['~RGBA4444TextureTest',  # Flakier than they are important.
-                  '~RGB565TextureTest'])
-
-  if 'Vulkan' in bot and 'Adreno' in bot:
-    # skia:5777
-    match.extend(['~XfermodeImageFilterCroppedInput',
-                  '~GrTextureStripAtlasFlush',
-                  '~CopySurface'])
-
-  if blacklisted:
-    args.append('--blacklist')
-    args.extend(blacklisted)
-
-  if match:
-    args.append('--match')
-    args.extend(match)
-
-  # These bots run out of memory running RAW codec tests. Do not run them in
-  # parallel
-  if ('NexusPlayer' in bot or 'Nexus5' in bot or 'Nexus9' in bot
-      or 'Win8-MSVC-ShuttleB' in bot):
-    args.append('--noRAW_threading')
-
-  return args
-
-
-def key_params(api):
-  """Build a unique key from the builder name (as a list).
-
-  E.g.  arch x86 gpu GeForce320M mode MacMini4.1 os Mac10.6
-  """
-  # Don't bother to include role, which is always Test.
-  # TryBots are uploaded elsewhere so they can use the same key.
-  blacklist = ['role', 'is_trybot']
-
-  flat = []
-  for k in sorted(api.vars.builder_cfg.keys()):
-    if k not in blacklist:
-      flat.append(k)
-      flat.append(api.vars.builder_cfg[k])
-  return flat
-
-
-def test_steps(api):
-  """Run the DM test."""
-  use_hash_file = False
-  if api.vars.upload_dm_results:
-    # This must run before we write anything into
-    # api.flavor.device_dirs.dm_dir or we may end up deleting our
-    # output on machines where they're the same.
-    api.flavor.create_clean_host_dir(api.vars.dm_dir)
-    host_dm_dir = str(api.vars.dm_dir)
-    device_dm_dir = str(api.flavor.device_dirs.dm_dir)
-    if host_dm_dir != device_dm_dir:
-      api.flavor.create_clean_device_dir(device_dm_dir)
-
-    # Obtain the list of already-generated hashes.
-    hash_filename = 'uninteresting_hashes.txt'
-
-    # Ensure that the tmp_dir exists.
-    api.run.run_once(api.file.makedirs,
-                           'tmp_dir',
-                           api.vars.tmp_dir,
-                           infra_step=True)
-
-    host_hashes_file = api.vars.tmp_dir.join(hash_filename)
-    hashes_file = api.flavor.device_path_join(
-        api.flavor.device_dirs.tmp_dir, hash_filename)
-    api.run(
-        api.python.inline,
-        'get uninteresting hashes',
-        program="""
-        import contextlib
-        import math
-        import socket
-        import sys
-        import time
-        import urllib2
-
-        HASHES_URL = 'https://gold.skia.org/_/hashes'
-        RETRIES = 5
-        TIMEOUT = 60
-        WAIT_BASE = 15
-
-        socket.setdefaulttimeout(TIMEOUT)
-        for retry in range(RETRIES):
-          try:
-            with contextlib.closing(
-                urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:
-              hashes = w.read()
-              with open(sys.argv[1], 'w') as f:
-                f.write(hashes)
-                break
-          except Exception as e:
-            print 'Failed to get uninteresting hashes from %s:' % HASHES_URL
-            print e
-            if retry == RETRIES:
-              raise
-            waittime = WAIT_BASE * math.pow(2, retry)
-            print 'Retry in %d seconds.' % waittime
-            time.sleep(waittime)
-        """,
-        args=[host_hashes_file],
-        cwd=api.vars.skia_dir,
-        abort_on_failure=False,
-        fail_build_on_failure=False,
-        infra_step=True)
-
-    if api.path.exists(host_hashes_file):
-      api.flavor.copy_file_to_device(host_hashes_file, hashes_file)
-      use_hash_file = True
-
-  # Run DM.
-  properties = [
-    'gitHash',      api.vars.got_revision,
-    'master',       api.vars.master_name,
-    'builder',      api.vars.builder_name,
-    'build_number', api.vars.build_number,
-  ]
-  if api.vars.is_trybot:
-    properties.extend([
-      'issue',         api.vars.issue,
-      'patchset',      api.vars.patchset,
-      'patch_storage', api.vars.patch_storage,
-    ])
-  if api.vars.no_buildbot:
-    properties.extend(['no_buildbot', 'True'])
-    properties.extend(['swarming_bot_id', api.vars.swarming_bot_id])
-    properties.extend(['swarming_task_id', api.vars.swarming_task_id])
-
-  args = [
-    'dm',
-    '--undefok',   # This helps branches that may not know new flags.
-    '--resourcePath', api.flavor.device_dirs.resource_dir,
-    '--skps', api.flavor.device_dirs.skp_dir,
-    '--images', api.flavor.device_path_join(
-        api.flavor.device_dirs.images_dir, 'dm'),
-    '--colorImages', api.flavor.device_path_join(
-        api.flavor.device_dirs.images_dir, 'colorspace'),
-    '--nameByHash',
-    '--properties'
-  ] + properties
-
-  args.extend(['--svgs', api.flavor.device_dirs.svg_dir])
-
-  args.append('--key')
-  args.extend(key_params(api))
-  if use_hash_file:
-    args.extend(['--uninterestingHashesFile', hashes_file])
-  if api.vars.upload_dm_results:
-    args.extend(['--writePath', api.flavor.device_dirs.dm_dir])
-
-  skip_flag = None
-  if api.vars.builder_cfg.get('cpu_or_gpu') == 'CPU':
-    skip_flag = '--nogpu'
-  elif api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU':
-    skip_flag = '--nocpu'
-  if skip_flag:
-    args.append(skip_flag)
-  args.extend(dm_flags(api.vars.builder_name))
-
-  api.run(api.flavor.step, 'dm', cmd=args,
-          abort_on_failure=False,
-          env=api.vars.default_env)
-
-  if api.vars.upload_dm_results:
-    # Copy images and JSON to host machine if needed.
-    api.flavor.copy_directory_contents_to_host(
-        api.flavor.device_dirs.dm_dir, api.vars.dm_dir)
-
-  # See skia:2789.
-  if ('Valgrind' in api.vars.builder_name and
-      api.vars.builder_cfg.get('cpu_or_gpu') == 'GPU'):
-    abandonGpuContext = list(args)
-    abandonGpuContext.append('--abandonGpuContext')
-    api.run(api.flavor.step, 'dm --abandonGpuContext',
-                  cmd=abandonGpuContext, abort_on_failure=False)
-    preAbandonGpuContext = list(args)
-    preAbandonGpuContext.append('--preAbandonGpuContext')
-    api.run(api.flavor.step, 'dm --preAbandonGpuContext',
-                  cmd=preAbandonGpuContext, abort_on_failure=False,
-                  env=api.vars.default_env)
-
-
 def RunSteps(api):
-  api.core.setup()
-  try:
-    api.flavor.install_everything()
-    test_steps(api)
-  finally:
-    api.flavor.cleanup_steps()
-  api.run.check_failure()
+  api.sktest.run()
 
 
 def GenTests(api):
-  for mastername, slaves in TEST_BUILDERS.iteritems():
-    for slavename, builders_by_slave in slaves.iteritems():
-      for builder in builders_by_slave:
-        test = (
-          api.test(builder) +
-          api.properties(buildername=builder,
-                         mastername=mastername,
-                         slavename=slavename,
-                         buildnumber=5,
-                         revision='abc123',
-                         path_config='kitchen',
-                         swarm_out_dir='[SWARM_OUT_DIR]') +
-          api.path.exists(
-              api.path['start_dir'].join('skia'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skimage', 'VERSION'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'skp', 'VERSION'),
-              api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                           'svg', 'VERSION'),
-              api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-          )
-        )
-        if 'Trybot' in builder:
-          test += api.properties(issue=500,
-                                 patchset=1,
-                                 rietveld='https://codereview.chromium.org')
-        if 'Win' in builder:
-          test += api.platform('win', 64)
-
-
-        yield test
-
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
   yield (
-    api.test('failed_dm') +
-    api.properties(buildername=builder,
-                   mastername='client.skia',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=6,
+    api.test('Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug') +
+    api.properties(buildername='Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug',
+                   mastername='fake-master',
+                   slavename='fake-slave',
+                   buildnumber=5,
                    revision='abc123',
                    path_config='kitchen',
                    swarm_out_dir='[SWARM_OUT_DIR]') +
     api.path.exists(
         api.path['start_dir'].join('skia'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
+                                   'skimage', 'VERSION'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
+                                   'skp', 'VERSION'),
         api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
+                                   'svg', 'VERSION'),
         api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.step_data('dm', retcode=1)
-  )
-
-  builder = 'Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-GN_Android'
-  yield (
-    api.test('failed_get_hashes') +
-    api.properties(buildername=builder,
-                   mastername='client.skia',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=6,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.step_data('get uninteresting hashes', retcode=1)
-  )
-
-  builder = 'Test-iOS-Clang-iPad4-GPU-SGX554-Arm7-Debug'
-  yield (
-    api.test('missing_SKP_VERSION_device') +
-    api.properties(buildername=builder,
-                   mastername='client.skia',
-                   slavename='skiabot-linux-swarm-000',
-                   buildnumber=6,
-                   revision='abc123',
-                   path_config='kitchen',
-                   swarm_out_dir='[SWARM_OUT_DIR]') +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.step_data('read SKP_VERSION', retcode=1)
-  )
-
-  builder = 'Test-Win8-MSVC-ShuttleB-CPU-AVX2-x86_64-Release-Trybot'
-  yield (
-    api.test('big_issue_number') +
-    api.properties(buildername=builder,
-                     mastername='client.skia.compile',
-                     slavename='skiabot-linux-swarm-000',
-                     buildnumber=5,
-                     revision='abc123',
-                     path_config='kitchen',
-                     swarm_out_dir='[SWARM_OUT_DIR]',
-                     rietveld='https://codereview.chromium.org',
-                     patchset=1,
-                     issue=2147533002L) +
-    api.path.exists(
-        api.path['start_dir'].join('skia'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skimage', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'skp', 'VERSION'),
-        api.path['start_dir'].join('skia', 'infra', 'bots', 'assets',
-                                     'svg', 'VERSION'),
-        api.path['start_dir'].join('tmp', 'uninteresting_hashes.txt')
-    ) +
-    api.platform('win', 64)
-  )
-
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug-Trybot'
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=builder,
-          mastername='client.skia',
-          slavename='skiabot-linux-swarm-000',
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      )
-  )
-
-  yield (
-      api.test('nobuildbot') +
-      api.properties(
-          buildername=builder,
-          mastername='client.skia',
-          slavename='skiabot-linux-swarm-000',
-          buildnumber=5,
-          path_config='kitchen',
-          swarm_out_dir='[SWARM_OUT_DIR]',
-          revision='abc123',
-          nobuildbot='True',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      ) +
-      api.step_data('get swarming bot id',
-          stdout=api.raw_io.output('skia-bot-123')) +
-      api.step_data('get swarming task id', stdout=api.raw_io.output('123456'))
+    )
   )
diff --git a/infra/bots/recipes/upload_dm_results.expected/normal_bot.json b/infra/bots/recipes/upload_dm_results.expected/upload.json
similarity index 100%
rename from infra/bots/recipes/upload_dm_results.expected/normal_bot.json
rename to infra/bots/recipes/upload_dm_results.expected/upload.json
diff --git a/infra/bots/recipes/upload_dm_results.py b/infra/bots/recipes/upload_dm_results.py
index 946c77b..d495013 100644
--- a/infra/bots/recipes/upload_dm_results.py
+++ b/infra/bots/recipes/upload_dm_results.py
@@ -7,147 +7,19 @@
 
 
 DEPS = [
-  'build/file',
-  'recipe_engine/json',
-  'recipe_engine/path',
+  'upload_dm_results',
   'recipe_engine/properties',
-  'recipe_engine/shutil',
-  'recipe_engine/step',
-  'recipe_engine/time',
 ]
 
 
-import calendar
-
-
-DM_JSON = 'dm.json'
-GS_BUCKET = 'gs://skia-infra-gm'
-UPLOAD_ATTEMPTS = 5
-VERBOSE_LOG = 'verbose.log'
-
-
-def cp(api, name, src, dst, extra_args=None):
-  cmd = ['gsutil', 'cp']
-  if extra_args:
-    cmd.extend(extra_args)
-  cmd.extend([src, dst])
-
-  name = 'upload %s' % name
-  for i in xrange(UPLOAD_ATTEMPTS):
-    step_name = name
-    if i > 0:
-      step_name += ' (attempt %d)' % (i+1)
-    try:
-      api.step(step_name, cmd=cmd)
-      break
-    except api.step.StepFailure:
-      if i == UPLOAD_ATTEMPTS - 1:
-        raise
-
-
 def RunSteps(api):
-  builder_name = api.properties['buildername']
-  revision = api.properties['revision']
-
-  results_dir = api.path['start_dir'].join('dm')
-
-  # Move dm.json and verbose.log to their own directory.
-  json_file = results_dir.join(DM_JSON)
-  log_file = results_dir.join(VERBOSE_LOG)
-  tmp_dir = api.path['start_dir'].join('tmp_upload')
-  api.shutil.makedirs('tmp dir', tmp_dir, infra_step=True)
-  api.shutil.copy('copy dm.json', json_file, tmp_dir)
-  api.shutil.copy('copy verbose.log', log_file, tmp_dir)
-  api.shutil.remove('rm old dm.json', json_file)
-  api.shutil.remove('rm old verbose.log', log_file)
-
-  # Upload the images.
-  image_dest_path = '/'.join((GS_BUCKET, 'dm-images-v1'))
-  files_to_upload = api.file.glob(
-      'find images',
-      results_dir.join('*'),
-      test_data=['someimage.png'],
-      infra_step=True)
-  if len(files_to_upload) > 0:
-    cp(api, 'images', results_dir.join('*'), image_dest_path)
-
-  # Upload the JSON summary and verbose.log.
-  now = api.time.utcnow()
-  summary_dest_path = '/'.join([
-      'dm-json-v1',
-      str(now.year ).zfill(4),
-      str(now.month).zfill(2),
-      str(now.day  ).zfill(2),
-      str(now.hour ).zfill(2),
-      revision,
-      builder_name,
-      str(int(calendar.timegm(now.utctimetuple())))])
-
-  # Trybot results are further siloed by issue/patchset.
-  issue = str(api.properties.get('issue', ''))
-  patchset = str(api.properties.get('patchset', ''))
-  if api.properties.get('patch_storage', '') == 'gerrit':
-    issue = str(api.properties['patch_issue'])
-    patchset = str(api.properties['patch_set'])
-  if issue and patchset:
-    summary_dest_path = '/'.join((
-        'trybot', summary_dest_path, issue, patchset))
-
-  summary_dest_path = '/'.join((GS_BUCKET, summary_dest_path))
-
-  cp(api, 'JSON and logs', tmp_dir.join('*'), summary_dest_path,
-     ['-z', 'json,log'])
+  api.upload_dm_results.run()
 
 
 def GenTests(api):
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
   yield (
-    api.test('normal_bot') +
-    api.properties(buildername=builder,
+    api.test('upload') +
+    api.properties(buildername='Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug',
                    revision='abc123',
                    path_config='kitchen')
   )
-
-  yield (
-    api.test('failed_once') +
-    api.properties(buildername=builder,
-                   revision='abc123',
-                   path_config='kitchen') +
-    api.step_data('upload images', retcode=1)
-  )
-
-  yield (
-    api.test('failed_all') +
-    api.properties(buildername=builder,
-                   revision='abc123',
-                   path_config='kitchen') +
-    api.step_data('upload images', retcode=1) +
-    api.step_data('upload images (attempt 2)', retcode=1) +
-    api.step_data('upload images (attempt 3)', retcode=1) +
-    api.step_data('upload images (attempt 4)', retcode=1) +
-    api.step_data('upload images (attempt 5)', retcode=1)
-  )
-
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
-  yield (
-    api.test('trybot') +
-    api.properties(buildername=builder,
-                   revision='abc123',
-                   path_config='kitchen',
-                   issue='12345',
-                   patchset='1002')
-  )
-
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=builder,
-          revision='abc123',
-          path_config='kitchen',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      )
-  )
diff --git a/infra/bots/recipes/upload_nano_results.expected/normal_bot.json b/infra/bots/recipes/upload_nano_results.expected/upload.json
similarity index 100%
rename from infra/bots/recipes/upload_nano_results.expected/normal_bot.json
rename to infra/bots/recipes/upload_nano_results.expected/upload.json
diff --git a/infra/bots/recipes/upload_nano_results.py b/infra/bots/recipes/upload_nano_results.py
index f55fbce..26fe666 100644
--- a/infra/bots/recipes/upload_nano_results.py
+++ b/infra/bots/recipes/upload_nano_results.py
@@ -7,81 +7,19 @@
 
 
 DEPS = [
-  'build/file',
-  'recipe_engine/path',
   'recipe_engine/properties',
-  'recipe_engine/step',
-  'recipe_engine/time',
+  'upload_nano_results',
 ]
 
 
 def RunSteps(api):
-  # Upload the nanobench resuls.
-  builder_name = api.properties['buildername']
-
-  now = api.time.utcnow()
-  src_path = api.path['start_dir'].join(
-      'perfdata', builder_name, 'data')
-  results = api.file.glob(
-      'find results',
-      '*.json',
-      cwd=src_path,
-      test_data=['nanobench_abc123.json'],
-      infra_step=True)
-  if len(results) != 1:  # pragma: nocover
-    raise Exception('Unable to find nanobench or skpbench JSON file!')
-
-  src = src_path.join(results[0])
-  basename = api.path.basename(src)
-  gs_path = '/'.join((
-      'nano-json-v1', str(now.year).zfill(4),
-      str(now.month).zfill(2), str(now.day).zfill(2), str(now.hour).zfill(2),
-      builder_name))
-
-  issue = str(api.properties.get('issue', ''))
-  patchset = str(api.properties.get('patchset', ''))
-  if api.properties.get('patch_storage', '') == 'gerrit':
-    issue = str(api.properties['patch_issue'])
-    patchset = str(api.properties['patch_set'])
-  if issue and patchset:
-    gs_path = '/'.join(('trybot', gs_path, issue, patchset))
-
-  dst = '/'.join(('gs://skia-perf', gs_path, basename))
-
-  api.step('upload',
-           cmd=['gsutil', 'cp', '-a', 'public-read', '-z', 'json', src, dst],
-           infra_step=True)
+  api.upload_nano_results.run()
 
 
 def GenTests(api):
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug'
   yield (
-    api.test('normal_bot') +
-    api.properties(buildername=builder,
+    api.test('upload') +
+    api.properties(buildername='Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug',
                    revision='abc123',
                    path_config='kitchen')
   )
-
-  builder = 'Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Debug-Trybot'
-  yield (
-    api.test('trybot') +
-    api.properties(buildername=builder,
-                   revision='abc123',
-                   path_config='kitchen',
-                   issue='12345',
-                   patchset='1002')
-  )
-
-  yield (
-      api.test('recipe_with_gerrit_patch') +
-      api.properties(
-          buildername=builder,
-          revision='abc123',
-          path_config='kitchen',
-          patch_storage='gerrit') +
-      api.properties.tryserver(
-          buildername=builder,
-          gerrit_project='skia',
-          gerrit_url='https://skia-review.googlesource.com/',
-      )
-  )
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index 3e1757d..18813bd 100644
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -174,6 +174,12 @@
         "Build-Ubuntu-Clang-x86_64-Release"
       ]
     },
+    "Build-Ubuntu-Clang-x86_64-Release-Fast": {
+      "priority": 0.8,
+      "tasks": [
+        "Build-Ubuntu-Clang-x86_64-Release-Fast"
+      ]
+    },
     "Build-Ubuntu-Clang-x86_64-Release-TSAN": {
       "priority": 0.8,
       "tasks": [
@@ -222,12 +228,6 @@
         "Build-Ubuntu-GCC-x86_64-Release-ANGLE"
       ]
     },
-    "Build-Ubuntu-GCC-x86_64-Release-Fast": {
-      "priority": 0.8,
-      "tasks": [
-        "Build-Ubuntu-GCC-x86_64-Release-Fast"
-      ]
-    },
     "Build-Ubuntu-GCC-x86_64-Release-Mesa": {
       "priority": 0.8,
       "tasks": [
@@ -530,12 +530,6 @@
         "Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-Android"
       ]
     },
-    "Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Android": {
-      "priority": 0.8,
-      "tasks": [
-        "Upload-Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Android"
-      ]
-    },
     "Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Debug-Android": {
       "priority": 0.8,
       "tasks": [
@@ -662,6 +656,12 @@
         "Upload-Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release"
       ]
     },
+    "Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast"
+      ]
+    },
     "Perf-Ubuntu-Clang-Golo-GPU-GT610-x86_64-Debug-ASAN": {
       "priority": 0.8,
       "tasks": [
@@ -728,6 +728,42 @@
         "Upload-Perf-Win10-MSVC-Golo-GPU-GT610-x86_64-Release"
       ]
     },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug": {
+      "priority": 0.8,
+      "tasks": [
+        "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug"
+      ]
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE": {
+      "priority": 0.8,
+      "tasks": [
+        "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE"
+      ]
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan": {
+      "priority": 0.8,
+      "tasks": [
+        "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan"
+      ]
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release"
+      ]
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE"
+      ]
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan"
+      ]
+    },
     "Perf-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Debug-ANGLE": {
       "priority": 0.8,
       "tasks": [
@@ -740,6 +776,18 @@
         "Upload-Perf-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Release-ANGLE"
       ]
     },
+    "Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug": {
+      "priority": 0.8,
+      "tasks": [
+        "Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug"
+      ]
+    },
+    "Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release"
+      ]
+    },
     "Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug": {
       "priority": 0.8,
       "tasks": [
@@ -764,6 +812,18 @@
         "Upload-Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Release-Vulkan"
       ]
     },
+    "Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug": {
+      "priority": 0.8,
+      "tasks": [
+        "Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug"
+      ]
+    },
+    "Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release"
+      ]
+    },
     "Perf-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug": {
       "priority": 0.8,
       "tasks": [
@@ -800,6 +860,42 @@
         "Upload-Perf-Win10-MSVC-ShuttleC-GPU-iHD530-x86_64-Release"
       ]
     },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug": {
+      "priority": 0.8,
+      "tasks": [
+        "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug"
+      ]
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE": {
+      "priority": 0.8,
+      "tasks": [
+        "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE"
+      ]
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan": {
+      "priority": 0.8,
+      "tasks": [
+        "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan"
+      ]
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release"
+      ]
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE"
+      ]
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan"
+      ]
+    },
     "Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86-Debug": {
       "priority": 0.8,
       "tasks": [
@@ -824,30 +920,6 @@
         "Upload-Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Release"
       ]
     },
-    "Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug": {
-      "priority": 0.8,
-      "tasks": [
-        "Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug"
-      ]
-    },
-    "Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release": {
-      "priority": 0.8,
-      "tasks": [
-        "Upload-Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release"
-      ]
-    },
-    "Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug": {
-      "priority": 0.8,
-      "tasks": [
-        "Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug"
-      ]
-    },
-    "Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release": {
-      "priority": 0.8,
-      "tasks": [
-        "Upload-Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release"
-      ]
-    },
     "Perf-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Debug": {
       "priority": 0.8,
       "tasks": [
@@ -1124,6 +1196,12 @@
         "Upload-Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release"
       ]
     },
+    "Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast"
+      ]
+    },
     "Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-TSAN": {
       "priority": 0.8,
       "tasks": [
@@ -1185,12 +1263,6 @@
         "Upload-Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release"
       ]
     },
-    "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast": {
-      "priority": 0.8,
-      "tasks": [
-        "Upload-Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast"
-      ]
-    },
     "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD": {
       "priority": 0.8,
       "tasks": [
@@ -1221,6 +1293,42 @@
         "Upload-Test-Win10-MSVC-Golo-GPU-GT610-x86_64-Release"
       ]
     },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug"
+      ]
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE"
+      ]
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan"
+      ]
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release"
+      ]
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE"
+      ]
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan"
+      ]
+    },
     "Test-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Debug-ANGLE": {
       "priority": 0.8,
       "tasks": [
@@ -1233,6 +1341,18 @@
         "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Release-ANGLE"
       ]
     },
+    "Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug"
+      ]
+    },
+    "Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release"
+      ]
+    },
     "Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug": {
       "priority": 0.8,
       "tasks": [
@@ -1251,6 +1371,18 @@
         "Upload-Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Release"
       ]
     },
+    "Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug"
+      ]
+    },
+    "Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release"
+      ]
+    },
     "Test-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug": {
       "priority": 0.8,
       "tasks": [
@@ -1287,6 +1419,42 @@
         "Upload-Test-Win10-MSVC-ShuttleC-GPU-iHD530-x86_64-Release"
       ]
     },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug"
+      ]
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE"
+      ]
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan"
+      ]
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release"
+      ]
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE"
+      ]
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan": {
+      "priority": 0.8,
+      "tasks": [
+        "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan"
+      ]
+    },
     "Test-Win2k8-MSVC-GCE-CPU-AVX2-x86-Debug": {
       "priority": 0.8,
       "tasks": [
@@ -1323,30 +1491,6 @@
         "Upload-Test-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Release-GDI"
       ]
     },
-    "Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug": {
-      "priority": 0.8,
-      "tasks": [
-        "Upload-Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug"
-      ]
-    },
-    "Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release": {
-      "priority": 0.8,
-      "tasks": [
-        "Upload-Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release"
-      ]
-    },
-    "Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug": {
-      "priority": 0.8,
-      "tasks": [
-        "Upload-Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug"
-      ]
-    },
-    "Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release": {
-      "priority": 0.8,
-      "tasks": [
-        "Upload-Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release"
-      ]
-    },
     "Test-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Debug": {
       "priority": 0.8,
       "tasks": [
@@ -2240,6 +2384,38 @@
       "isolate": "compile_skia.isolate",
       "priority": 0.8
     },
+    "Build-Ubuntu-Clang-x86_64-Release-Fast": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/clang_linux",
+          "path": "clang_linux",
+          "version": "version:4"
+        }
+      ],
+      "dimensions": [
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_compile",
+        "repository=<(REPO)",
+        "buildername=Build-Ubuntu-Clang-x86_64-Release-Fast",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "compile_skia.isolate",
+      "priority": 0.8
+    },
     "Build-Ubuntu-Clang-x86_64-Release-TSAN": {
       "cipd_packages": [
         {
@@ -2447,31 +2623,6 @@
       "isolate": "compile_skia.isolate",
       "priority": 0.8
     },
-    "Build-Ubuntu-GCC-x86_64-Release-Fast": {
-      "dimensions": [
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_compile",
-        "repository=<(REPO)",
-        "buildername=Build-Ubuntu-GCC-x86_64-Release-Fast",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "compile_skia.isolate",
-      "priority": 0.8
-    },
     "Build-Ubuntu-GCC-x86_64-Release-Mesa": {
       "dimensions": [
         "gpu:none",
@@ -2684,7 +2835,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2716,7 +2867,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2748,7 +2899,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2780,7 +2931,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2812,7 +2963,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2844,7 +2995,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2876,7 +3027,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2908,7 +3059,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2940,7 +3091,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -2972,7 +3123,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -3004,7 +3155,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         },
         {
           "name": "skia/bots/win_vulkan_sdk",
@@ -3041,7 +3192,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -3073,7 +3224,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -3105,7 +3256,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         }
       ],
       "dimensions": [
@@ -3137,7 +3288,7 @@
         {
           "name": "skia/bots/win_toolchain",
           "path": "t",
-          "version": "version:5"
+          "version": "version:6"
         },
         {
           "name": "skia/bots/win_vulkan_sdk",
@@ -3297,7 +3448,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3346,7 +3497,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3395,7 +3546,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3444,7 +3595,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3493,7 +3644,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3542,7 +3693,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3591,7 +3742,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3640,7 +3791,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3689,7 +3840,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3738,7 +3889,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3787,7 +3938,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3836,7 +3987,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3885,7 +4036,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3934,7 +4085,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3983,7 +4134,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -3995,7 +4146,7 @@
         "Build-Ubuntu-Clang-arm-Debug-Android"
       ],
       "dimensions": [
-        "device_os:MOB31E",
+        "device_os:M4B30Z",
         "device_type:hammerhead",
         "os:Android",
         "pool:Skia"
@@ -4032,7 +4183,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4044,7 +4195,7 @@
         "Build-Ubuntu-Clang-arm-Release-Android"
       ],
       "dimensions": [
-        "device_os:MOB31E",
+        "device_os:M4B30Z",
         "device_type:hammerhead",
         "os:Android",
         "pool:Skia"
@@ -4081,7 +4232,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4130,7 +4281,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4179,7 +4330,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4228,7 +4379,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4277,7 +4428,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4326,7 +4477,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4375,7 +4526,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4414,55 +4565,6 @@
       "isolate": "perf_skia.isolate",
       "priority": 0.8
     },
-    "Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Android": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Ubuntu-Clang-x86-Release-Android"
-      ],
-      "dimensions": [
-        "device_os:NRD90R",
-        "device_type:fugu",
-        "os:Android",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_perf",
-        "repository=<(REPO)",
-        "buildername=Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Android",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "perf_skia.isolate",
-      "priority": 0.8
-    },
     "Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Debug-Android": {
       "cipd_packages": [
         {
@@ -4473,7 +4575,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4522,7 +4624,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4571,7 +4673,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4583,7 +4685,7 @@
         "Build-Ubuntu-Clang-arm64-Debug-Android"
       ],
       "dimensions": [
-        "device_os:NMF25",
+        "device_os:NMF26Q",
         "device_type:sailfish",
         "os:Android",
         "pool:Skia"
@@ -4620,7 +4722,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4632,7 +4734,7 @@
         "Build-Ubuntu-Clang-arm64-Debug-Android_Vulkan"
       ],
       "dimensions": [
-        "device_os:NMF25",
+        "device_os:NMF26Q",
         "device_type:sailfish",
         "os:Android",
         "pool:Skia"
@@ -4669,7 +4771,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4681,7 +4783,7 @@
         "Build-Ubuntu-Clang-arm64-Release-Android"
       ],
       "dimensions": [
-        "device_os:NMF25",
+        "device_os:NMF26Q",
         "device_type:sailfish",
         "os:Android",
         "pool:Skia"
@@ -4718,7 +4820,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4730,7 +4832,7 @@
         "Build-Ubuntu-Clang-arm64-Release-Android_Vulkan"
       ],
       "dimensions": [
-        "device_os:NMF25",
+        "device_os:NMF26Q",
         "device_type:sailfish",
         "os:Android",
         "pool:Skia"
@@ -4767,7 +4869,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4816,7 +4918,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4860,7 +4962,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         }
       ],
       "dependencies": [
@@ -4899,7 +5001,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         }
       ],
       "dependencies": [
@@ -4943,7 +5045,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -4991,7 +5093,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5039,7 +5141,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5088,7 +5190,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5137,7 +5239,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5185,7 +5287,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5233,7 +5335,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5281,7 +5383,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5330,7 +5432,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5384,7 +5486,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5438,7 +5540,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5477,6 +5579,55 @@
       "isolate": "perf_skia.isolate",
       "priority": 0.8
     },
+    "Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Ubuntu-Clang-x86_64-Release-Fast"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
     "Perf-Ubuntu-Clang-Golo-GPU-GT610-x86_64-Debug-ASAN": {
       "cipd_packages": [
         {
@@ -5487,7 +5638,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5540,7 +5691,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5589,7 +5740,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5638,7 +5789,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5687,7 +5838,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5786,7 +5937,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5834,7 +5985,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5882,7 +6033,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5930,7 +6081,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5968,6 +6119,294 @@
       "isolate": "perf_skia.isolate",
       "priority": 0.8
     },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
     "Perf-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Debug-ANGLE": {
       "cipd_packages": [
         {
@@ -5978,7 +6417,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -5991,7 +6430,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6026,7 +6465,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6039,7 +6478,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6064,6 +6503,102 @@
       "isolate": "perf_skia.isolate",
       "priority": 0.8
     },
+    "Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:1002:683d",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:1002:683d",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
     "Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug": {
       "cipd_packages": [
         {
@@ -6074,7 +6609,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6087,7 +6622,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6122,7 +6657,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6135,7 +6670,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6170,7 +6705,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6183,7 +6718,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6218,7 +6753,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6231,7 +6766,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6256,6 +6791,102 @@
       "isolate": "perf_skia.isolate",
       "priority": 0.8
     },
+    "Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:8086:0412",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:8086:0412",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
     "Perf-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug": {
       "cipd_packages": [
         {
@@ -6266,7 +6897,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6279,7 +6910,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6314,7 +6945,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6327,7 +6958,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6362,7 +6993,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6375,7 +7006,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6410,7 +7041,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6423,7 +7054,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6458,7 +7089,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6471,7 +7102,7 @@
       ],
       "dimensions": [
         "gpu:8086:1912",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6506,7 +7137,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6519,7 +7150,7 @@
       ],
       "dimensions": [
         "gpu:8086:1912",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -6544,6 +7175,294 @@
       "isolate": "perf_skia.isolate",
       "priority": 0.8
     },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
+    "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_perf",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "perf_skia.isolate",
+      "priority": 0.8
+    },
     "Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86-Debug": {
       "cipd_packages": [
         {
@@ -6554,7 +7473,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6603,7 +7522,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6652,7 +7571,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6701,7 +7620,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6740,198 +7659,6 @@
       "isolate": "perf_skia.isolate",
       "priority": 0.8
     },
-    "Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Win-MSVC-x86_64-Debug"
-      ],
-      "dimensions": [
-        "gpu:1002:683d",
-        "os:Windows-8.1-SP0",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_perf",
-        "repository=<(REPO)",
-        "buildername=Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "perf_skia.isolate",
-      "priority": 0.8
-    },
-    "Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Win-MSVC-x86_64-Release"
-      ],
-      "dimensions": [
-        "gpu:1002:683d",
-        "os:Windows-8.1-SP0",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_perf",
-        "repository=<(REPO)",
-        "buildername=Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "perf_skia.isolate",
-      "priority": 0.8
-    },
-    "Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Win-MSVC-x86_64-Debug"
-      ],
-      "dimensions": [
-        "gpu:8086:0412",
-        "os:Windows-8.1-SP0",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_perf",
-        "repository=<(REPO)",
-        "buildername=Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "perf_skia.isolate",
-      "priority": 0.8
-    },
-    "Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Win-MSVC-x86_64-Release"
-      ],
-      "dimensions": [
-        "gpu:8086:0412",
-        "os:Windows-8.1-SP0",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_perf",
-        "repository=<(REPO)",
-        "buildername=Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "perf_skia.isolate",
-      "priority": 0.8
-    },
     "Perf-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Debug": {
       "cipd_packages": [
         {
@@ -6942,7 +7669,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -6990,7 +7717,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7038,7 +7765,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7087,7 +7814,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7136,7 +7863,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7185,7 +7912,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7234,7 +7961,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7283,7 +8010,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7332,7 +8059,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7381,7 +8108,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7430,7 +8157,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7479,7 +8206,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7528,7 +8255,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7577,7 +8304,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7626,7 +8353,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7675,7 +8402,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7724,7 +8451,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7773,7 +8500,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7785,7 +8512,7 @@
         "Build-Ubuntu-Clang-arm-Debug-Android"
       ],
       "dimensions": [
-        "device_os:MOB31E",
+        "device_os:M4B30Z",
         "device_type:hammerhead",
         "os:Android",
         "pool:Skia"
@@ -7822,7 +8549,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7834,7 +8561,7 @@
         "Build-Ubuntu-Clang-arm-Release-Android"
       ],
       "dimensions": [
-        "device_os:MOB31E",
+        "device_os:M4B30Z",
         "device_type:hammerhead",
         "os:Android",
         "pool:Skia"
@@ -7871,7 +8598,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7920,7 +8647,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -7969,7 +8696,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8018,7 +8745,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8067,7 +8794,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8116,7 +8843,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8165,7 +8892,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8214,7 +8941,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8263,7 +8990,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8312,7 +9039,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8361,7 +9088,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8410,7 +9137,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8459,7 +9186,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8471,7 +9198,7 @@
         "Build-Ubuntu-Clang-arm64-Debug-Android"
       ],
       "dimensions": [
-        "device_os:NMF25",
+        "device_os:NMF26Q",
         "device_type:marlin",
         "os:Android",
         "pool:Skia"
@@ -8508,7 +9235,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8520,7 +9247,7 @@
         "Build-Ubuntu-Clang-arm64-Debug-Android_Vulkan"
       ],
       "dimensions": [
-        "device_os:NMF25",
+        "device_os:NMF26Q",
         "device_type:marlin",
         "os:Android",
         "pool:Skia"
@@ -8557,7 +9284,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8569,7 +9296,7 @@
         "Build-Ubuntu-Clang-arm64-Release-Android"
       ],
       "dimensions": [
-        "device_os:NMF25",
+        "device_os:NMF26Q",
         "device_type:marlin",
         "os:Android",
         "pool:Skia"
@@ -8606,7 +9333,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8618,7 +9345,7 @@
         "Build-Ubuntu-Clang-arm64-Release-Android_Vulkan"
       ],
       "dimensions": [
-        "device_os:NMF25",
+        "device_os:NMF26Q",
         "device_type:marlin",
         "os:Android",
         "pool:Skia"
@@ -8655,7 +9382,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8703,7 +9430,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8751,7 +9478,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8800,7 +9527,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8849,7 +9576,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8897,7 +9624,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8945,7 +9672,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -8993,7 +9720,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9042,7 +9769,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9096,7 +9823,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9150,7 +9877,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9189,6 +9916,55 @@
       "isolate": "test_skia.isolate",
       "priority": 0.8
     },
+    "Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Ubuntu-Clang-x86_64-Release-Fast"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
     "Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-TSAN": {
       "cipd_packages": [
         {
@@ -9199,7 +9975,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9253,7 +10029,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9306,7 +10082,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9359,7 +10135,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9408,7 +10184,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9532,7 +10308,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9581,7 +10357,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9620,55 +10396,6 @@
       "isolate": "test_skia.isolate",
       "priority": 0.8
     },
-    "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Ubuntu-GCC-x86_64-Release-Fast"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_test",
-        "repository=<(REPO)",
-        "buildername=Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "test_skia.isolate",
-      "priority": 0.8
-    },
     "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD": {
       "cipd_packages": [
         {
@@ -9679,7 +10406,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9728,7 +10455,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9776,7 +10503,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9824,7 +10551,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9872,7 +10599,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9910,6 +10637,294 @@
       "isolate": "test_skia.isolate",
       "priority": 0.8
     },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:8086:1926",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
     "Test-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Debug-ANGLE": {
       "cipd_packages": [
         {
@@ -9920,7 +10935,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9933,7 +10948,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -9968,7 +10983,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -9981,7 +10996,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10006,6 +11021,102 @@
       "isolate": "test_skia.isolate",
       "priority": 0.8
     },
+    "Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:1002:683d",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:1002:683d",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
     "Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug": {
       "cipd_packages": [
         {
@@ -10016,7 +11127,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10029,7 +11140,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10064,7 +11175,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10077,7 +11188,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10112,7 +11223,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10125,7 +11236,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10150,6 +11261,102 @@
       "isolate": "test_skia.isolate",
       "priority": 0.8
     },
+    "Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:8086:0412",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:8086:0412",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
     "Test-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug": {
       "cipd_packages": [
         {
@@ -10160,7 +11367,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10173,7 +11380,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10208,7 +11415,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10221,7 +11428,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10256,7 +11463,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10269,7 +11476,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10304,7 +11511,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10317,7 +11524,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10352,7 +11559,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10365,7 +11572,7 @@
       ],
       "dimensions": [
         "gpu:8086:1912",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10400,7 +11607,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10413,7 +11620,7 @@
       ],
       "dimensions": [
         "gpu:8086:1912",
-        "os:Windows-10-10586",
+        "os:Windows-10-14393",
         "pool:Skia"
       ],
       "execution_timeout_ns": 14400000000000,
@@ -10438,6 +11645,294 @@
       "isolate": "test_skia.isolate",
       "priority": 0.8
     },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
+    "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan": {
+      "cipd_packages": [
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:17"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:32"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:3"
+        }
+      ],
+      "dependencies": [
+        "Build-Win-MSVC-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1ba1",
+        "os:Windows-10-14393",
+        "pool:Skia"
+      ],
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "swarm_test",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "io_timeout_ns": 2400000000000,
+      "isolate": "test_skia.isolate",
+      "priority": 0.8
+    },
     "Test-Win2k8-MSVC-GCE-CPU-AVX2-x86-Debug": {
       "cipd_packages": [
         {
@@ -10448,7 +11943,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10497,7 +11992,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10546,7 +12041,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10595,7 +12090,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10644,7 +12139,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10693,7 +12188,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10732,198 +12227,6 @@
       "isolate": "test_skia.isolate",
       "priority": 0.8
     },
-    "Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Win-MSVC-x86_64-Debug"
-      ],
-      "dimensions": [
-        "gpu:1002:683d",
-        "os:Windows-8.1-SP0",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_test",
-        "repository=<(REPO)",
-        "buildername=Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "test_skia.isolate",
-      "priority": 0.8
-    },
-    "Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Win-MSVC-x86_64-Release"
-      ],
-      "dimensions": [
-        "gpu:1002:683d",
-        "os:Windows-8.1-SP0",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_test",
-        "repository=<(REPO)",
-        "buildername=Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "test_skia.isolate",
-      "priority": 0.8
-    },
-    "Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Win-MSVC-x86_64-Debug"
-      ],
-      "dimensions": [
-        "gpu:8086:0412",
-        "os:Windows-8.1-SP0",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_test",
-        "repository=<(REPO)",
-        "buildername=Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "test_skia.isolate",
-      "priority": 0.8
-    },
-    "Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/skimage",
-          "path": "skimage",
-          "version": "version:17"
-        },
-        {
-          "name": "skia/bots/skp",
-          "path": "skp",
-          "version": "version:29"
-        },
-        {
-          "name": "skia/bots/svg",
-          "path": "svg",
-          "version": "version:3"
-        }
-      ],
-      "dependencies": [
-        "Build-Win-MSVC-x86_64-Release"
-      ],
-      "dimensions": [
-        "gpu:8086:0412",
-        "os:Windows-8.1-SP0",
-        "pool:Skia"
-      ],
-      "execution_timeout_ns": 14400000000000,
-      "expiration_ns": 72000000000000,
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "swarm_test",
-        "repository=<(REPO)",
-        "buildername=Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "io_timeout_ns": 2400000000000,
-      "isolate": "test_skia.isolate",
-      "priority": 0.8
-    },
     "Test-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Debug": {
       "cipd_packages": [
         {
@@ -10934,7 +12237,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -10982,7 +12285,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:29"
+          "version": "version:32"
         },
         {
           "name": "skia/bots/svg",
@@ -11339,35 +12642,6 @@
       "isolate": "upload_nano_results.isolate",
       "priority": 0.8
     },
-    "Upload-Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Android": {
-      "dependencies": [
-        "Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Android"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "upload_nano_results",
-        "repository=<(REPO)",
-        "buildername=Perf-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-Android",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "upload_nano_results.isolate",
-      "priority": 0.8
-    },
     "Upload-Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-Android": {
       "dependencies": [
         "Perf-Android-Clang-NexusPlayer-GPU-PowerVR-x86-Release-Android"
@@ -11687,6 +12961,35 @@
       "isolate": "upload_nano_results.isolate",
       "priority": 0.8
     },
+    "Upload-Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast": {
+      "dependencies": [
+        "Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release": {
       "dependencies": [
         "Perf-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release"
@@ -11774,6 +13077,93 @@
       "isolate": "upload_nano_results.isolate",
       "priority": 0.8
     },
+    "Upload-Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release": {
+      "dependencies": [
+        "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE": {
+      "dependencies": [
+        "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan": {
+      "dependencies": [
+        "Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Perf-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Release-ANGLE": {
       "dependencies": [
         "Perf-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Release-ANGLE"
@@ -11803,6 +13193,35 @@
       "isolate": "upload_nano_results.isolate",
       "priority": 0.8
     },
+    "Upload-Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release": {
+      "dependencies": [
+        "Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Release": {
       "dependencies": [
         "Perf-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Release"
@@ -11861,6 +13280,35 @@
       "isolate": "upload_nano_results.isolate",
       "priority": 0.8
     },
+    "Upload-Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release": {
+      "dependencies": [
+        "Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Perf-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Release": {
       "dependencies": [
         "Perf-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Release"
@@ -11948,6 +13396,93 @@
       "isolate": "upload_nano_results.isolate",
       "priority": 0.8
     },
+    "Upload-Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release": {
+      "dependencies": [
+        "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE": {
+      "dependencies": [
+        "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan": {
+      "dependencies": [
+        "Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_nano_results",
+        "repository=<(REPO)",
+        "buildername=Perf-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_nano_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Release": {
       "dependencies": [
         "Perf-Win2k8-MSVC-GCE-CPU-AVX2-x86_64-Release"
@@ -11977,64 +13512,6 @@
       "isolate": "upload_nano_results.isolate",
       "priority": 0.8
     },
-    "Upload-Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release": {
-      "dependencies": [
-        "Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "upload_nano_results",
-        "repository=<(REPO)",
-        "buildername=Perf-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "upload_nano_results.isolate",
-      "priority": 0.8
-    },
-    "Upload-Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release": {
-      "dependencies": [
-        "Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "upload_nano_results",
-        "repository=<(REPO)",
-        "buildername=Perf-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "upload_nano_results.isolate",
-      "priority": 0.8
-    },
     "Upload-Perf-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Release": {
       "dependencies": [
         "Perf-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Release"
@@ -13282,6 +14759,35 @@
       "isolate": "upload_dm_results.isolate",
       "priority": 0.8
     },
+    "Upload-Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast": {
+      "dependencies": [
+        "Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Ubuntu-Clang-GCE-CPU-AVX2-x86_64-Release-Fast",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug": {
       "dependencies": [
         "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86-Debug"
@@ -13398,35 +14904,6 @@
       "isolate": "upload_dm_results.isolate",
       "priority": 0.8
     },
-    "Upload-Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast": {
-      "dependencies": [
-        "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "upload_dm_results",
-        "repository=<(REPO)",
-        "buildername=Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-Fast",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "upload_dm_results.isolate",
-      "priority": 0.8
-    },
     "Upload-Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD": {
       "dependencies": [
         "Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD"
@@ -13543,6 +15020,180 @@
       "isolate": "upload_dm_results.isolate",
       "priority": 0.8
     },
+    "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug": {
+      "dependencies": [
+        "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE": {
+      "dependencies": [
+        "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan": {
+      "dependencies": [
+        "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Debug-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release": {
+      "dependencies": [
+        "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE": {
+      "dependencies": [
+        "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan": {
+      "dependencies": [
+        "Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-NUC-GPU-IntelIris540-x86_64-Release-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Test-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Debug-ANGLE": {
       "dependencies": [
         "Test-Win10-MSVC-NUC-GPU-IntelIris6100-x86_64-Debug-ANGLE"
@@ -13601,6 +15252,64 @@
       "isolate": "upload_dm_results.isolate",
       "priority": 0.8
     },
+    "Upload-Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug": {
+      "dependencies": [
+        "Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release": {
+      "dependencies": [
+        "Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ShuttleA-GPU-AMDHD7770-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug": {
       "dependencies": [
         "Test-Win10-MSVC-ShuttleA-GPU-GTX660-x86_64-Debug"
@@ -13688,6 +15397,64 @@
       "isolate": "upload_dm_results.isolate",
       "priority": 0.8
     },
+    "Upload-Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug": {
+      "dependencies": [
+        "Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release": {
+      "dependencies": [
+        "Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ShuttleB-GPU-IntelHD4600-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Test-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug": {
       "dependencies": [
         "Test-Win10-MSVC-ShuttleC-GPU-GTX960-x86_64-Debug"
@@ -13862,6 +15629,180 @@
       "isolate": "upload_dm_results.isolate",
       "priority": 0.8
     },
+    "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug": {
+      "dependencies": [
+        "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE": {
+      "dependencies": [
+        "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan": {
+      "dependencies": [
+        "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Debug-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release": {
+      "dependencies": [
+        "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE": {
+      "dependencies": [
+        "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-ANGLE",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
+    "Upload-Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan": {
+      "dependencies": [
+        "Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-avx2",
+        "gpu:none",
+        "os:Ubuntu-14.04",
+        "pool:Skia"
+      ],
+      "extra_args": [
+        "--workdir",
+        "../../..",
+        "upload_dm_results",
+        "repository=<(REPO)",
+        "buildername=Test-Win10-MSVC-ZBOX-GPU-GTX1070-x86_64-Release-Vulkan",
+        "mastername=fake-master",
+        "buildnumber=2",
+        "slavename=fake-buildslave",
+        "nobuildbot=True",
+        "swarm_out_dir=${ISOLATED_OUTDIR}",
+        "revision=<(REVISION)",
+        "patch_storage=<(PATCH_STORAGE)",
+        "patch_issue=<(ISSUE)",
+        "patch_set=<(PATCHSET)"
+      ],
+      "isolate": "upload_dm_results.isolate",
+      "priority": 0.8
+    },
     "Upload-Test-Win2k8-MSVC-GCE-CPU-AVX2-x86-Debug": {
       "dependencies": [
         "Test-Win2k8-MSVC-GCE-CPU-AVX2-x86-Debug"
@@ -14036,122 +15977,6 @@
       "isolate": "upload_dm_results.isolate",
       "priority": 0.8
     },
-    "Upload-Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug": {
-      "dependencies": [
-        "Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "upload_dm_results",
-        "repository=<(REPO)",
-        "buildername=Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Debug",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "upload_dm_results.isolate",
-      "priority": 0.8
-    },
-    "Upload-Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release": {
-      "dependencies": [
-        "Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "upload_dm_results",
-        "repository=<(REPO)",
-        "buildername=Test-Win8-MSVC-ShuttleA-GPU-HD7770-x86_64-Release",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "upload_dm_results.isolate",
-      "priority": 0.8
-    },
-    "Upload-Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug": {
-      "dependencies": [
-        "Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "upload_dm_results",
-        "repository=<(REPO)",
-        "buildername=Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Debug",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "upload_dm_results.isolate",
-      "priority": 0.8
-    },
-    "Upload-Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release": {
-      "dependencies": [
-        "Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release"
-      ],
-      "dimensions": [
-        "cpu:x86-64-avx2",
-        "gpu:none",
-        "os:Ubuntu-14.04",
-        "pool:Skia"
-      ],
-      "extra_args": [
-        "--workdir",
-        "../../..",
-        "upload_dm_results",
-        "repository=<(REPO)",
-        "buildername=Test-Win8-MSVC-ShuttleB-GPU-HD4600-x86_64-Release",
-        "mastername=fake-master",
-        "buildnumber=2",
-        "slavename=fake-buildslave",
-        "nobuildbot=True",
-        "swarm_out_dir=${ISOLATED_OUTDIR}",
-        "revision=<(REVISION)",
-        "patch_storage=<(PATCH_STORAGE)",
-        "patch_issue=<(ISSUE)",
-        "patch_set=<(PATCHSET)"
-      ],
-      "isolate": "upload_dm_results.isolate",
-      "priority": 0.8
-    },
     "Upload-Test-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Debug": {
       "dependencies": [
         "Test-iOS-Clang-iPadMini4-GPU-GX6450-Arm7-Debug"
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index 49a4704..76eba81 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -5,17 +5,17 @@
   project_id: "build"
   url: "https://chromium.googlesource.com/chromium/tools/build.git"
   branch: "master"
-  revision: "e33bd98272c8f19dd707f0fd6422a171d5ba4115"
+  revision: "396d0f5c499e0b9647026a0af93052148da28d22"
 }
 deps {
   project_id: "depot_tools"
   url: "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
   branch: "master"
-  revision: "dd013e2b68644091823d1749af6b5236cfa2a252"
+  revision: "99790f42d633be44c16516aed08c9010aab15826"
 }
 deps {
   project_id: "recipe_engine"
   url: "https://chromium.googlesource.com/external/github.com/luci/recipes-py.git"
   branch: "master"
-  revision: "63c1741d42b2e5e77af929cbdf7f69c03f35b627"
+  revision: "7c73d4ed7ea86551395704ec24c2bfdfc07acc48"
 }
diff --git a/platform_tools/android/apps/build.gradle b/platform_tools/android/apps/build.gradle
index c16bfe4..ee279e8 100644
--- a/platform_tools/android/apps/build.gradle
+++ b/platform_tools/android/apps/build.gradle
@@ -6,7 +6,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.1.0'
+        classpath 'com.android.tools.build:gradle:2.2.3'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
@@ -19,21 +19,27 @@
     }
 }
 
-def setupSkiaLibraryBuild(project, appVariants, buildCmd, requireCMake = false) {
+def setupSkiaLibraryBuild(project, appVariants, appName) {
     appVariants.all{ variant ->
-        def buildNativeLib = project.task("${variant.name}_SkiaNativeLib", type:Exec) {
+        def buildNativeLib = project.task("${variant.name}_BuildSkiaLib", type:Exec) {
             workingDir '../../../..' // top-level skia directory
-            commandLine constructBuildCommand(variant, buildCmd).split()
-            environment PATH: getPathWithDeps(requireCMake)
-            environment ANDROID_SDK_ROOT: getSDKPath()
+            commandLine constructBuildCommand(project, variant, appName).split()
         }
         buildNativeLib.onlyIf { !project.hasProperty("suppressNativeBuild") }
+
+        def copyNativeLib = project.task("${variant.name}_CopySkiaLib", type:Copy) {
+            from getVariantOutDir(project, variant).skiaOut
+            into getVariantOutDir(project, variant).androidOut
+            include "${appName}.so"
+        }
+
         TaskCollection<Task> compileTask = project.tasks.matching {
             //  println(it.name)
             it.name.toLowerCase().contains("compile" + variant.name.toLowerCase()) &&
                     it.name.toLowerCase().endsWith("ndk")
         }
-        compileTask.getAt(0).dependsOn buildNativeLib
+        compileTask.getAt(0).dependsOn copyNativeLib
+        copyNativeLib.dependsOn buildNativeLib
     }
 }
 
@@ -50,70 +56,58 @@
     return properties
 }
 
-def getSDKPath() {
-    String path = System.getenv("ANDROID_SDK_ROOT")
-    if (path == null) {
-        path = getLocalProperties().getProperty('sdk.dir', null)
-    }
-
-    if (path == null) {
-        throw new GradleScriptException("Android SDK not found! Please set ANDROID_SDK_ROOT to" +
-                                        " your path or define sdk.dir in gradle.properties")
-    }
-    return path
-}
-
-def getPathWithDeps(requireCMake = false) {
-    String path = System.getenv("PATH")
-    if (!path.contains("depot_tools")) {
-        path += ":" + getLocalProperties().getProperty('depot_tools.dir', null)
-    }
-
-    if (!path.contains("depot_tools")) {
-        throw GradleScriptException("Depot Tools not found! Please update your path to include" +
-                                    " depot_tools or define depot_tools.dir in gradle.properties")
-    }
-
-    if (requireCMake) {
-        String cmakePath = getSDKPath() + "/cmake/bin"
-        if (!file(cmakePath).exists()) {
-            logger.warn("cmake not found! Please install the android SDK version of cmake.");
-        }
-        if (!path.contains(cmakePath)) {
-            path = cmakePath + ":" + path
-        }
-    }
-
-    return path
-}
-
-def constructBuildCommand(variant, buildTarget) {
-    String cmdLine = "./platform_tools/android/bin/android_ninja $buildTarget"
-    String deviceType = null
+def getVariantOutDir(project, variant) {
+    String variantPrefix = null
+    String androidLibDir = null
     if (variant.name.startsWith("arm64")) {
-        deviceType = "arm64"
+        variantPrefix = "arm64"
+        androidLibDir = "arm64-v8a"
     } else if (variant.name.startsWith("arm")) {
-        deviceType = "arm_v7_neon"
+        variantPrefix = "arm"
+        androidLibDir = "armeabi-v7a"
     } else if (variant.name.startsWith("x86_64")) {
-        deviceType = "x86_64"
+        variantPrefix = "x64"
+        androidLibDir = "x86_64"
     } else if (variant.name.startsWith("x86")) {
-        deviceType = "x86"
-    } else if (variant.name.startsWith("mips")) {
-        deviceType = "mips"
-    } else if (variant.name.startsWith("mips64")) {
-        deviceType = "mips64"
+        variantPrefix = "x86"
+        androidLibDir = "x86"
+    } else if (variant.name.startsWith("mipsel")) {
+        variantPrefix = "mipsel"
+        androidLibDir = "mips"
+    } else if (variant.name.startsWith("mips64el")) {
+        variantPrefix = "mips64el"
+        androidLibDir = "mips64"
     }
 
-    if (deviceType != null) {
-        cmdLine += " -d " + deviceType
+    String skiaOutDir = null
+    String propName = "${variantPrefix}.out.dir"
+    if (project.hasProperty(propName)) {
+        skiaOutDir = project.getProperties().getAt(propName)
+    } else {
+        skiaOutDir = getLocalProperties().getProperty(propName, "missing_variant_out")
     }
 
-    if (variant.name.endsWith("Release")) {
-        cmdLine += " --release"
+    return [skiaOut: skiaOutDir,
+            androidOut: "src/main/libs/${androidLibDir}"]
+}
+
+def constructBuildCommand(project, variant, appName) {
+    String depotToolsDir = null
+    for (String entry : System.getenv("PATH").split(":")) {
+        if (entry.contains("depot_tools")) {
+            depotToolsDir = entry;
+            break;
+        }
+    }
+    if (depotToolsDir == null) {
+        depotToolsDir = getLocalProperties().getProperty('depot_tools.dir', null)
     }
 
-    if (variant.name.indexOf("vulkan") != -1) {
-        cmdLine += " --vulkan"
+    if (depotToolsDir == null) {
+        throw GradleScriptException("Depot Tools not found! Please update your path to include" +
+                " depot_tools or define depot_tools.dir in local.properties")
     }
-    return cmdLine
-}
\ No newline at end of file
+
+    String out_dir = getVariantOutDir(project, variant).skiaOut
+    return "${depotToolsDir}/ninja -C $out_dir $appName"
+}
diff --git a/platform_tools/android/apps/canvasproof/build.gradle b/platform_tools/android/apps/canvasproof/build.gradle
deleted file mode 100644
index 5493699..0000000
--- a/platform_tools/android/apps/canvasproof/build.gradle
+++ /dev/null
@@ -1,23 +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.
- */
-apply plugin: 'com.android.application'
-android {
-    compileSdkVersion 19
-    buildToolsVersion "22.0.1"
-    defaultConfig {
-        applicationId "org.skia.canvasproof"
-        minSdkVersion 9
-        targetSdkVersion 19
-        versionCode 1
-        versionName "1.0"
-        signingConfig signingConfigs.debug
-    }
-    sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call
-    sourceSets.main.jniLibs.srcDir "src/main/libs"
-    productFlavors { arm {}; arm64 {}; x86 {}; x86_64 {}; mips {}; mips64 {}; }
-    setupSkiaLibraryBuild(project, applicationVariants, "CopyCanvasProofDeps")
-}
diff --git a/platform_tools/android/apps/canvasproof/src/main/AndroidManifest.xml b/platform_tools/android/apps/canvasproof/src/main/AndroidManifest.xml
deleted file mode 100644
index be774c8..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright 2015 Google Inc.
-
-  Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file.
--->
-<!-- BEGIN_INCLUDE(manifest) -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="org.skia.canvasproof"
-          android:versionCode="1"
-          android:versionName="1.0">
-    <uses-sdk android:minSdkVersion="9" />
-    <uses-feature android:glEsVersion="0x00020000" />
-    <application android:label="Canvas Proof">
-        <activity android:name="org.skia.canvasproof.CanvasProofActivity"
-                  android:label="Canvas Proof"
-                  android:configChanges="orientation|keyboardHidden"
-                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
-            <meta-data android:name="android.app.lib_name"
-                       android:value="canvasproof" />
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
-<!-- END_INCLUDE(manifest) -->
diff --git a/platform_tools/android/apps/canvasproof/src/main/assets/skps/.gitignore b/platform_tools/android/apps/canvasproof/src/main/assets/skps/.gitignore
deleted file mode 100644
index 4a70801..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/assets/skps/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.skp
diff --git a/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/CanvasProofActivity.java b/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/CanvasProofActivity.java
deleted file mode 100644
index 9363585..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/CanvasProofActivity.java
+++ /dev/null
@@ -1,205 +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.
- */
-
-package org.skia.canvasproof;
-
-import android.app.Activity;
-import android.content.res.AssetManager;
-import android.graphics.Picture;
-import android.opengl.GLSurfaceView;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout.LayoutParams;
-import android.widget.LinearLayout;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class CanvasProofActivity extends Activity {
-    private static final String TAG = "CanvasProofActivity";
-    private GaneshPictureRenderer ganeshPictureRenderer;
-    private HwuiPictureView hwuiPictureView;
-    private GLSurfaceView ganeshPictureView;
-    private LinearLayout splitPaneView;
-    private View currentView;
-    private float x, y;
-    private int resourcesIndex;
-    private class PictureAsset {
-        public String path;
-        public long ptr;
-        public Picture picture;
-    };
-    private PictureAsset[] assets;
-
-    @SuppressWarnings("deprecation")  // purposely using this
-    private static Picture ReadPicture(InputStream inputStream)
-        throws IOException {
-        Picture p = null;
-        try {
-            p = Picture.createFromStream(inputStream);
-        } catch (java.lang.Exception e) {
-            Log.e(TAG, "Exception in Picture.createFromStream", e);
-        }
-        inputStream.close();
-        return p;
-    }
-
-    private void getAssetPaths() {
-        String directory = "skps";
-        AssetManager mgr = this.getAssets();
-        assert (mgr != null);
-        String[] resources;
-        try {
-            resources = mgr.list(directory);
-        } catch (IOException e) {
-            Log.e(TAG, "IOException in getAssetPaths", e);
-            return;
-        }
-        if (resources == null || resources.length == 0) {
-            Log.e(TAG, "SKP assets should be packaged in " +
-                  ".../apps/canvasproof/src/main/assets/skps/" +
-                  ", but none were found.");
-            return;
-        }
-        CreateSkiaPicture.init();
-        this.assets = new PictureAsset[resources.length];
-        for (int i = 0; i < resources.length; ++i) {
-            String path = directory + File.separator + resources[i];
-            this.assets[i] = new PictureAsset();
-            this.assets[i].path = path;
-            try {
-                this.assets[i].ptr = CreateSkiaPicture.create(mgr.open(path));
-                if (0 == this.assets[i].ptr) {
-                    Log.e(TAG, "CreateSkiaPicture.create returned 0 " + path);
-                }
-                Picture p = CanvasProofActivity.ReadPicture(mgr.open(path));
-                if (null == p) {
-                    Log.e(TAG, "CanvasProofActivity.ReadPicture.create " +
-                          "returned null " + path);
-                } else if (0 == p.getHeight() || 0 == p.getWidth()) {
-                    Log.e(TAG, "CanvasProofActivity.ReadPicture.create " +
-                          "empty picture" + path);
-                    p = null;
-                }
-                this.assets[i].picture = p;
-            } catch (IOException e) {
-                Log.e(TAG, "IOException in getAssetPaths " + path + e);
-                return;
-            }
-        }
-    }
-
-    private void nextView() {
-        if (this.currentView == this.hwuiPictureView) {
-            this.currentView = this.ganeshPictureView;
-            this.ganeshPictureView.setRenderMode(
-                    GLSurfaceView.RENDERMODE_CONTINUOUSLY);
-        } else if (this.currentView == null ||
-                   this.currentView == this.ganeshPictureView) {
-            this.setContentView(new View(this));
-            LayoutParams layoutParams =
-                new LayoutParams(
-                        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0.5f);
-            this.ganeshPictureView.setLayoutParams(layoutParams);
-            this.ganeshPictureView.setRenderMode(
-                    GLSurfaceView.RENDERMODE_WHEN_DIRTY);
-            this.splitPaneView.addView(this.ganeshPictureView);
-            this.hwuiPictureView.setLayoutParams(layoutParams);
-            this.splitPaneView.addView(this.hwuiPictureView);
-            this.currentView = this.splitPaneView;
-            this.hwuiPictureView.fullTime = false;
-        } else if (this.currentView == this.splitPaneView) {
-            this.splitPaneView.removeAllViews();
-            this.currentView = this.hwuiPictureView;
-            this.hwuiPictureView.fullTime = true;
-        } else {
-            Log.e(TAG, "unexpected value");
-            this.setContentView(null);
-            return;
-        }
-        this.setContentView(this.currentView);
-        this.currentView.invalidate();
-    }
-
-    private void nextPicture(int d) {
-        if (this.assets == null) {
-            Log.w(TAG, "this.assets == null");
-            return;
-        }
-        assert (this.assets.length > 0);
-        resourcesIndex = (resourcesIndex + d) % this.assets.length;
-        while (resourcesIndex < 0) {
-            resourcesIndex += this.assets.length;
-        }
-        while (resourcesIndex >= this.assets.length) {
-            resourcesIndex -= this.assets.length;
-        }
-        this.ganeshPictureRenderer.setPicture(assets[resourcesIndex].ptr);
-        this.hwuiPictureView.setPicture(assets[resourcesIndex].picture);
-        this.currentView.invalidate();
-    }
-
-    @Override
-    protected void onStop() {
-        this.ganeshPictureRenderer.releaseResources();
-        super.onStop();
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        this.getAssetPaths();
-        this.ganeshPictureRenderer = new GaneshPictureRenderer();
-        this.hwuiPictureView = new HwuiPictureView(this);
-
-        this.ganeshPictureRenderer.setScale(2.0f);
-        this.hwuiPictureView.setScale(2.0f);
-        this.ganeshPictureView = ganeshPictureRenderer.makeView(this);
-        this.splitPaneView = new LinearLayout(this);
-        this.splitPaneView.setOrientation(LinearLayout.VERTICAL);
-        this.splitPaneView.setGravity(Gravity.FILL);
-
-        LayoutParams layoutParams =
-            new LayoutParams(
-                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0.5f);
-        this.ganeshPictureView.setLayoutParams(layoutParams);
-        this.hwuiPictureView.setLayoutParams(layoutParams);
-
-        this.nextView();
-        this.nextPicture(0);
-    }
-
-    // TODO: replace this funtion with onTouchEvent().
-    // @Override public boolean onTouchEvent(MotionEvent event)...
-    @Override
-    public boolean dispatchTouchEvent (MotionEvent event) {
-        switch(event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                this.x = event.getX();
-                this.y = event.getY();
-                break;
-            case MotionEvent.ACTION_UP:
-                float dx = event.getX() - this.x;
-                float dy = event.getY() - this.y;
-                float dx2 = dx * dx;
-                float dy2 = dy * dy;
-                if (dx2 + dy2 > 22500.0) {
-                    if (dy2 < dx2) {
-                        this.nextPicture(dx > 0 ? 1 : -1);
-                    } else if (dy > 0) {
-                        this.nextView();
-                    }
-                }
-                break;
-        }
-        return super.onTouchEvent(event);
-    }
-}
diff --git a/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/CreateSkiaPicture.java b/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/CreateSkiaPicture.java
deleted file mode 100644
index 61aa14a..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/CreateSkiaPicture.java
+++ /dev/null
@@ -1,58 +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.
- */
-/*
-AJAR=$ANDROID_SDK_ROOT/platforms/android-19/android.jar
-CLASS=CreateSkiaPicture
-SRC=platform_tools/android/apps/canvasproof/src/main
-javac -classpath $AJAR $SRC/java/org/skia/canvasproof/$CLASS.java
-javah -classpath $AJAR:$SRC/java -d $SRC/jni org.skia.canvasproof.$CLASS
-*/
-
-package org.skia.canvasproof;
-
-import android.util.Log;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.UnsatisfiedLinkError;
-
-public class CreateSkiaPicture {
-    private static final String TAG = "CreateSkiaPicture";
-
-    public static void init() {
-        try {
-            System.loadLibrary("skia_android");
-            System.loadLibrary("canvasproof");
-        } catch (java.lang.Error e) {
-            Log.v(TAG, "System.loadLibrary error", e);
-        }
-    }
-
-    public static long create(InputStream inputStream) throws IOException {
-        byte[] buffer = new byte[16 * (1 << 10)];  // 16 KByte
-        long p = 0;
-        try {
-            p = CreateSkiaPicture.createImpl(inputStream, buffer);
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "UnsatisfiedLinkError createImpl");
-        }
-        inputStream.close();
-        return p;
-    }
-
-    public static void delete(long ptr) {
-        try {
-            if (ptr != 0) {
-                CreateSkiaPicture.deleteImpl(ptr);
-            }
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "UnsatisfiedLinkError deleteImpl");
-        }
-
-    }
-    private static native void deleteImpl(long ptr);
-    private static native long createImpl(InputStream s, byte[] b);
-}
diff --git a/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/GaneshPictureRenderer.java b/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/GaneshPictureRenderer.java
deleted file mode 100644
index 01c6dc3..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/GaneshPictureRenderer.java
+++ /dev/null
@@ -1,120 +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.
- */
-
-// AJAR=$ANDROID_SDK_ROOT/platforms/android-19/android.jar
-// SRC=platform_tools/android/apps/canvasproof/src/main
-// javac -classpath $AJAR $SRC/java/org/skia/canvasproof/GaneshPictureRenderer.java
-// javah -classpath $AJAR:$SRC/java -d $SRC/jni org.skia.canvasproof.GaneshPictureRenderer
-
-package org.skia.canvasproof;
-
-import android.app.Activity;
-import android.graphics.Rect;
-import android.opengl.GLSurfaceView;
-import android.util.Log;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
-public class GaneshPictureRenderer implements GLSurfaceView.Renderer {
-    private static final String TAG = "GaneshPictureRenderer";
-    private long picturePtr;
-    private long contextPtr;
-    private float scale;
-    private int width;
-    private int height;
-    private GLSurfaceView view;
-
-    GaneshPictureRenderer() {
-        try {
-            System.loadLibrary("skia_android");
-            System.loadLibrary("canvasproof");
-        } catch (java.lang.Error e) {
-            Log.e(TAG, "System.loadLibrary error", e);
-            return;
-        }
-        this.scale = 1;
-    }
-    public GLSurfaceView makeView(Activity activity) {
-        this.view = new GLSurfaceView(activity);
-        this.view.setEGLConfigChooser(8, 8, 8, 8, 0, 8);
-        this.view.setEGLContextClientVersion(2);
-        this.view.setRenderer(this);
-        this.view.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
-        return this.view;
-    }
-    static public Rect cullRect(long picturePtr) {
-        Rect rect = new Rect();
-        try {
-            GaneshPictureRenderer.GetCullRect(rect, picturePtr);
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "GetCullRect failed", e);
-        }
-        return rect;
-    }
-    public void setPicture(long picturePtr) {
-        this.picturePtr = picturePtr;
-        this.view.requestRender();
-    }
-    public void setScale(float s) { this.scale = s; }
-
-    public void releaseResources() {
-        if (this.contextPtr != 0) {
-            try {
-                GaneshPictureRenderer.CleanUp(this.contextPtr);
-            } catch (UnsatisfiedLinkError e) {
-                Log.e(TAG, "CleanUp failed", e);
-            }
-        }
-        this.contextPtr = 0;
-    }
-
-    private void createContext() {
-        try {
-            this.contextPtr = GaneshPictureRenderer.Ctor();
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "Ctor failed", e);
-        }
-    }
-
-    @Override
-    public void onSurfaceCreated(GL10 gl, EGLConfig c) {
-        this.releaseResources();
-        this.createContext();
-    }
-    @Override
-    public void onDrawFrame(GL10 gl) {
-        if (this.contextPtr == 0) {
-            this.createContext();
-        }
-        if (this.width > 0 && this.height > 0 &&
-            this.contextPtr != 0 && this.picturePtr != 0) {
-            try {
-                GaneshPictureRenderer.DrawThisFrame(
-                        this.width, this.height, this.scale,
-                        this.contextPtr, this.picturePtr);
-            } catch (UnsatisfiedLinkError e) {
-                Log.e(TAG, "DrawThisFrame failed", e);
-            }
-        }
-    }
-    @Override
-    public void onSurfaceChanged(GL10 gl, int w, int h) {
-        this.width = w;
-        this.height = h;
-    }
-    @Override
-    public void finalize() throws Throwable {
-        super.finalize();
-        this.releaseResources();
-    }
-
-    // Make the native functions static to simplify JNI interaction.
-    private static native void DrawThisFrame(int w, int h, float s, long pr, long pc);
-    private static native long Ctor();
-    private static native void CleanUp(long p);
-    private static native void GetCullRect(Rect r, long picture);
-}
diff --git a/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/HwuiPictureView.java b/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/HwuiPictureView.java
deleted file mode 100644
index 872089c..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/java/org/skia/canvasproof/HwuiPictureView.java
+++ /dev/null
@@ -1,72 +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.
- */
-
-package org.skia.canvasproof;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Picture;
-import android.util.Log;
-import android.view.View;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class HwuiPictureView extends View {
-    private static final String TAG = "HwuiPictureView";
-    private Picture picture;
-    private float scale;
-    private Picture defaultPicture;
-
-    public boolean fullTime;
-
-    HwuiPictureView(Context context) {
-        super(context);
-        this.scale = 1.0f;
-    }
-    public void setScale(float s) {
-        this.scale = s;
-    }
-    public void setPicture(Picture p) {
-        this.picture = p;
-        this.invalidate();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (this.picture != null) {
-            canvas.save();
-            canvas.scale(scale, scale);
-            HwuiPictureView.draw(canvas, this.picture);
-            canvas.restore();
-            if (fullTime) {
-                this.invalidate();
-            }
-        }
-    }
-
-    static private void draw(Canvas canvas, Picture p) {
-        if (android.os.Build.VERSION.SDK_INT > 22) {
-            try {
-                canvas.drawPicture(p);
-                return;
-            } catch (java.lang.Exception e) {
-                Log.e(TAG, "Exception while drawing picture in Hwui");
-            }
-        }
-        if (p.getWidth() > 0 && p.getHeight() > 0) {
-            // Fallback to software rendering.
-            Bitmap bm = Bitmap.createBitmap(p.getWidth(), p.getHeight(),
-                                            Bitmap.Config.ARGB_8888);
-            (new Canvas(bm)).drawPicture(p);
-            canvas.drawBitmap(bm, 0.0f, 0.0f, null);
-            bm.recycle();
-        }
-    }
-}
diff --git a/platform_tools/android/apps/canvasproof/src/main/jni/JavaInputStream.cpp b/platform_tools/android/apps/canvasproof/src/main/jni/JavaInputStream.cpp
deleted file mode 100644
index 823b72f..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/jni/JavaInputStream.cpp
+++ /dev/null
@@ -1,61 +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 "JavaInputStream.h"
-
-JavaInputStream::JavaInputStream(
-        JNIEnv* env, jbyteArray javaBuffer, jobject inputStream)
-    : fEnv(env)
-    , fStartIndex(0)
-    , fEndIndex(0) {
-    SkASSERT(inputStream);
-    SkASSERT(javaBuffer);
-    fInputStream = inputStream;
-    fJavaBuffer = javaBuffer;
-    fInputStreamClass = env->FindClass("java/io/InputStream");
-    SkASSERT(fInputStreamClass);
-    fReadMethodID = env->GetMethodID(fInputStreamClass, "read", "([B)I");
-    SkASSERT(fReadMethodID);
-}
-
-bool JavaInputStream::isAtEnd() const { return fStartIndex == fEndIndex; }
-
-size_t JavaInputStream::read(void* voidBuffer, size_t size) {
-    size_t totalRead = 0;
-    char* buffer = static_cast<char*>(voidBuffer);  // may be NULL;
-    while (size) {
-        // make sure the cache has at least one byte or is done.
-        if (fStartIndex == fEndIndex) {
-            jint count =
-                fEnv->CallIntMethod(fInputStream, fReadMethodID, fJavaBuffer);
-            if (fEnv->ExceptionCheck()) {
-                fEnv->ExceptionDescribe();
-                fEnv->ExceptionClear();
-                SkDebugf("---- java.io.InputStream::read() threw an exception\n");
-                return 0;
-            }
-            fStartIndex = 0;
-            fEndIndex = count;
-            if (this->isAtEnd()) {
-                return totalRead;  // No more to read.
-            }
-        }
-        SkASSERT(fEndIndex > fStartIndex);
-        size_t length = SkTMin(SkToSizeT(fEndIndex - fStartIndex), size);
-        if (buffer && length) {
-            jbyte* bufferElements
-                = fEnv->GetByteArrayElements(fJavaBuffer, NULL);
-            memcpy(buffer, &bufferElements[fStartIndex], length);
-            buffer += length;
-            fEnv->ReleaseByteArrayElements(fJavaBuffer, bufferElements, 0);
-        }
-        totalRead += length;
-        size -= length;
-        fStartIndex += length;
-    }
-    return totalRead;
-}
diff --git a/platform_tools/android/apps/canvasproof/src/main/jni/JavaInputStream.h b/platform_tools/android/apps/canvasproof/src/main/jni/JavaInputStream.h
deleted file mode 100644
index e14e026..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/jni/JavaInputStream.h
+++ /dev/null
@@ -1,28 +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 JavaInputStream_DEFINED
-#define JavaInputStream_DEFINED
-
-#include <jni.h>
-#include "SkStream.h"
-
-class JavaInputStream : public SkStream {
-public:
-    JavaInputStream(JNIEnv*, jbyteArray javaBuffer, jobject javaIoInputStream);
-    bool isAtEnd() const override;
-    size_t read(void*, size_t) override;
-private:
-    JNIEnv* fEnv;
-    jobject fInputStream;
-    jbyteArray fJavaBuffer;
-    jclass fInputStreamClass;
-    jmethodID fReadMethodID;
-    jint fStartIndex;
-    jint fEndIndex;
-};
-
-#endif  // JavaInputStream_DEFINED
diff --git a/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_CreateSkiaPicture.cpp b/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_CreateSkiaPicture.cpp
deleted file mode 100644
index 086cd5d..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_CreateSkiaPicture.cpp
+++ /dev/null
@@ -1,42 +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 "org_skia_canvasproof_CreateSkiaPicture.h"
-#include "JavaInputStream.h"
-#include "SkPicture.h"
-#include "SkPictureRecorder.h"
-
-/*
- * Class:     org_skia_canvasproof_CreateSkiaPicture
- * Method:    delete
- * Signature: (J)V
- */
-JNIEXPORT void JNICALL Java_org_skia_canvasproof_CreateSkiaPicture_deleteImpl(
-        JNIEnv* env, jclass clazz, jlong ptr) {
-    SkSafeUnref(reinterpret_cast<SkPicture*>(ptr));
-}
-
-/*
- * Class:     org_skia_canvasproof_CreateSkiaPicture
- * Method:    createImpl
- * Signature: (Ljava/io/InputStream;[B)J
- */
-JNIEXPORT jlong JNICALL Java_org_skia_canvasproof_CreateSkiaPicture_createImpl
-  (JNIEnv* env, jclass clazz, jobject inputStream, jbyteArray buffer) {
-    JavaInputStream stream(env, buffer, inputStream);
-    #if 0
-    sk_sp<SkPicture> p(SkPicture::CreateFromStream(&stream));
-    if (!p) { return 0; }
-    SkPictureRecorder recorder;
-    SkRect bounds = p->cullRect();
-    SkRTreeFactory bbh;
-    recorder.beginRecording(bounds, &bbh)->drawPicture(p);
-    return reinterpret_cast<long>(recorder.endRecordingAsPicture());
-    #else
-    return reinterpret_cast<long>(SkPicture::CreateFromStream(&stream));
-    #endif
-}
diff --git a/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_CreateSkiaPicture.h b/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_CreateSkiaPicture.h
deleted file mode 100644
index 2937d54..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_CreateSkiaPicture.h
+++ /dev/null
@@ -1,35 +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.
- */
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class org_skia_canvasproof_CreateSkiaPicture */
-
-#ifndef _Included_org_skia_canvasproof_CreateSkiaPicture
-#define _Included_org_skia_canvasproof_CreateSkiaPicture
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class:     org_skia_canvasproof_CreateSkiaPicture
- * Method:    deleteImpl
- * Signature: (J)V
- */
-JNIEXPORT void JNICALL Java_org_skia_canvasproof_CreateSkiaPicture_deleteImpl
-  (JNIEnv *, jclass, jlong);
-
-/*
- * Class:     org_skia_canvasproof_CreateSkiaPicture
- * Method:    createImpl
- * Signature: (Ljava/io/InputStream;[B)J
- */
-JNIEXPORT jlong JNICALL Java_org_skia_canvasproof_CreateSkiaPicture_createImpl
-  (JNIEnv *, jclass, jobject, jbyteArray);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_GaneshPictureRenderer.cpp b/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_GaneshPictureRenderer.cpp
deleted file mode 100644
index 1bdc655..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_GaneshPictureRenderer.cpp
+++ /dev/null
@@ -1,145 +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 "org_skia_canvasproof_GaneshPictureRenderer.h"
-
-#include "GrContext.h"
-#include "JavaInputStream.h"
-#include "SkCanvas.h"
-#include "SkMatrix.h"
-#include "SkPicture.h"
-#include "SkRect.h"
-#include "SkStream.h"
-#include "SkSurface.h"
-
-#define TAG "GaneshPictureRenderer.cpp: "
-
-static void render_picture(GrContext* grContext,
-                           int width,
-                           int height,
-                           const SkPicture* picture,
-                           const SkMatrix& matrix) {
-    SkASSERT(grContext);
-    if (!picture) {
-        SkDebugf(TAG "!picture\n");
-        return;
-    }
-    // Render to the default framebuffer render target.
-    GrBackendRenderTargetDesc desc;
-    desc.fWidth = width;
-    desc.fHeight = height;
-    desc.fConfig = kSkia8888_GrPixelConfig;
-    desc.fOrigin = kBottomLeft_GrSurfaceOrigin;
-    SkSurfaceProps surfaceProps(SkSurfaceProps::kUseDeviceIndependentFonts_Flag,
-                                kUnknown_SkPixelGeometry);
-    // TODO:  Check to see if we can keep the surface between draw calls.
-    sk_sp<SkSurface> surface(
-            SkSurface::MakeFromBackendRenderTarget(grContext, desc, nullptr, &surfaceProps));
-    if (surface) {
-        SkCanvas* canvas = surface->getCanvas();
-        SkASSERT(canvas);
-        canvas->clear(SK_ColorGRAY);
-        canvas->concat(matrix);
-        SkRect cullRect = picture->cullRect();
-        canvas->clipRect(cullRect);
-        picture->playback(canvas);
-        canvas->flush();
-    }
-}
-
-namespace {
-struct GaneshPictureRendererImpl {
-    sk_sp<GrContext> fGrContext;
-    void render(int w, int h, const SkPicture* p, const SkMatrix& m) {
-        if (!fGrContext) {
-            // Cache the rendering context between frames.
-            fGrContext.reset(GrContext::Create(kOpenGL_GrBackend, 0));
-            if (!fGrContext) {
-                SkDebugf(TAG "GrContext::Create - failed\n");
-                return;
-            }
-        }
-        render_picture(fGrContext, w, h, p, m);
-    }
-};
-}  // namespace
-
-/*
- * Class:     org_skia_canvasproof_GaneshPictureRenderer
- * Method:    DrawThisFrame
- * Signature: (IIFJ)V
- */
-JNIEXPORT void JNICALL Java_org_skia_canvasproof_GaneshPictureRenderer_DrawThisFrame(
-        JNIEnv*, jclass, jint width, jint height, jfloat scale, jlong ptr, jlong pic) {
-    if (!ptr) { return; }
-    SkMatrix matrix = SkMatrix::MakeScale((SkScalar)scale);
-    GaneshPictureRendererImpl* impl =
-        reinterpret_cast<GaneshPictureRendererImpl*>(ptr);
-    SkPicture* picture = reinterpret_cast<SkPicture*>(pic);
-    impl->render((int)width, (int)height, picture, matrix);
-}
-
-/*
- * Class:     org_skia_canvasproof_GaneshPictureRenderer
- * Method:    Ctor
- * Signature: ()J
- */
-JNIEXPORT jlong JNICALL Java_org_skia_canvasproof_GaneshPictureRenderer_Ctor
-  (JNIEnv *, jclass) {
-    return reinterpret_cast<jlong>(new GaneshPictureRendererImpl);
-}
-
-/*
- * Class:     org_skia_canvasproof_GaneshPictureRenderer
- * Method:    CleanUp
- * Signature: (J)V
- */
-JNIEXPORT void JNICALL Java_org_skia_canvasproof_GaneshPictureRenderer_CleanUp
-  (JNIEnv *, jclass, jlong ptr) {
-    delete reinterpret_cast<GaneshPictureRendererImpl*>(ptr);
-}
-
-namespace {
-struct AndroidRectHelper {
-    jfieldID fLeft, fTop, fRight, fBottom;
-    AndroidRectHelper()
-        : fLeft(nullptr), fTop(nullptr), fRight(nullptr), fBottom(nullptr) {}
-    void config(JNIEnv *env) {
-        if (!fLeft) {
-            jclass rectClass = env->FindClass("android/graphics/Rect");
-            SkASSERT(rectClass);
-            fLeft = env->GetFieldID(rectClass, "left", "I");
-            fTop = env->GetFieldID(rectClass, "top", "I");
-            fRight = env->GetFieldID(rectClass, "right", "I");
-            fBottom = env->GetFieldID(rectClass, "bottom", "I");
-        }
-    }
-};
-} // namespace
-
-/*
- * Class:     org_skia_canvasproof_GaneshPictureRenderer
- * Method:    GetCullRect
- * Signature: (Landroid/graphics/Rect;J)V
- */
-JNIEXPORT void JNICALL Java_org_skia_canvasproof_GaneshPictureRenderer_GetCullRect
-  (JNIEnv *env, jclass, jobject androidGraphicsRect, jlong picturePtr) {
-    SkASSERT(androidGraphicsRect);
-    const SkPicture* picture = reinterpret_cast<SkPicture*>(picturePtr);
-    SkRect rect = SkRect::MakeEmpty();
-    if (picture) {
-        rect = picture->cullRect();
-    }
-    SkIRect iRect;
-    rect.roundOut(&iRect);
-    static AndroidRectHelper help;
-    help.config(env);
-    env->SetIntField(androidGraphicsRect, help.fLeft, (jint)(iRect.left()));
-    env->SetIntField(androidGraphicsRect, help.fTop, (jint)(iRect.top()));
-    env->SetIntField(androidGraphicsRect, help.fRight, (jint)(iRect.right()));
-    env->SetIntField(androidGraphicsRect, help.fBottom, (jint)(iRect.bottom()));
-}
diff --git a/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_GaneshPictureRenderer.h b/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_GaneshPictureRenderer.h
deleted file mode 100644
index 401fa87..0000000
--- a/platform_tools/android/apps/canvasproof/src/main/jni/org_skia_canvasproof_GaneshPictureRenderer.h
+++ /dev/null
@@ -1,52 +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.
- */
-
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class org_skia_canvasproof_GaneshPictureRenderer */
-
-#ifndef _Included_org_skia_canvasproof_GaneshPictureRenderer
-#define _Included_org_skia_canvasproof_GaneshPictureRenderer
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class:     org_skia_canvasproof_GaneshPictureRenderer
- * Method:    DrawThisFrame
- * Signature: (IIFJJ)V
- */
-JNIEXPORT void JNICALL Java_org_skia_canvasproof_GaneshPictureRenderer_DrawThisFrame
-  (JNIEnv *, jclass, jint, jint, jfloat, jlong, jlong);
-
-/*
- * Class:     org_skia_canvasproof_GaneshPictureRenderer
- * Method:    Ctor
- * Signature: ()J
- */
-JNIEXPORT jlong JNICALL Java_org_skia_canvasproof_GaneshPictureRenderer_Ctor
-  (JNIEnv *, jclass);
-
-/*
- * Class:     org_skia_canvasproof_GaneshPictureRenderer
- * Method:    CleanUp
- * Signature: (J)V
- */
-JNIEXPORT void JNICALL Java_org_skia_canvasproof_GaneshPictureRenderer_CleanUp
-  (JNIEnv *, jclass, jlong);
-
-/*
- * Class:     org_skia_canvasproof_GaneshPictureRenderer
- * Method:    GetCullRect
- * Signature: (Landroid/graphics/Rect;J)V
- */
-JNIEXPORT void JNICALL Java_org_skia_canvasproof_GaneshPictureRenderer_GetCullRect
-  (JNIEnv *, jclass, jobject, jlong);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties b/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties
index be9e31c..9326d8c 100644
--- a/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties
+++ b/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Jul 07 11:56:32 EDT 2015
+#Fri Dec 02 09:57:59 EST 2016
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/platform_tools/android/apps/settings.gradle b/platform_tools/android/apps/settings.gradle
index faa3934..75ce0a6 100644
--- a/platform_tools/android/apps/settings.gradle
+++ b/platform_tools/android/apps/settings.gradle
@@ -1,2 +1 @@
-include ':canvasproof'
 include ':viewer'
diff --git a/platform_tools/android/apps/viewer/build.gradle b/platform_tools/android/apps/viewer/build.gradle
index 6da64be..e231a6f 100644
--- a/platform_tools/android/apps/viewer/build.gradle
+++ b/platform_tools/android/apps/viewer/build.gradle
@@ -24,7 +24,7 @@
     }
     sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call
     sourceSets.main.jniLibs.srcDir "src/main/libs"
-    productFlavors { arm {}; arm64 {}; x86 {}; x86_64 {}; mips {}; mips64 {}; arm64vulkan{}; }
+    productFlavors { arm {}; arm64 {}; x86 {}; x64 {}; mipsel {}; mips64el {}; arm64vulkan{}; }
 
-    setupSkiaLibraryBuild(project, applicationVariants, "CopyViewerDeps", true)
+    setupSkiaLibraryBuild(project, applicationVariants, "libviewer")
 }
diff --git a/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerApplication.java b/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerApplication.java
index 5849724..2dcce90 100644
--- a/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerApplication.java
+++ b/platform_tools/android/apps/viewer/src/main/java/org/skia/viewer/ViewerApplication.java
@@ -15,7 +15,6 @@
     private String mStateJsonStr, mTitle;
 
     static {
-        System.loadLibrary("skia_android");
         System.loadLibrary("viewer");
     }
 
diff --git a/platform_tools/android/bin/android_build_app b/platform_tools/android/bin/android_build_app
new file mode 100755
index 0000000..cd044c1
--- /dev/null
+++ b/platform_tools/android/bin/android_build_app
@@ -0,0 +1,66 @@
+#!/usr/bin/python
+#
+# 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 argparse
+import os
+import re
+import shutil
+import subprocess
+import sys
+
+parser = argparse.ArgumentParser(description='builds skia android apps')
+parser.add_argument('-C', '--output_dir', help='ninja out dir')
+parser.add_argument('app_name')
+
+args = parser.parse_args()
+
+target_cpu = "arm64"
+android_variant = ""
+android_buildtype = "debug"
+
+if args.output_dir == None:
+  sys.exit("unknown out directory")
+
+args_gn_path = os.path.join(args.output_dir, "args.gn")
+if os.path.exists(args_gn_path):
+  for line in open(args_gn_path):
+    m = re.match('target_cpu = "(.*)"', line.strip())
+    if m:
+      target_cpu = m.group(1)
+
+if target_cpu == "arm":
+  android_variant = "arm"
+elif target_cpu == "arm64":
+  android_variant = "arm64"
+elif target_cpu == "x86":
+  android_variant = "x86"
+elif target_cpu == "x64":
+  android_variant = "x86_64"
+elif target_cpu == "mipsel":
+  android_variant = "mips"
+elif target_cpu == "mips64el":
+  android_variant = "mips64"
+else:
+  sys.exit("unknown target_cpu")
+
+# build the apk using gradle
+try:
+    subprocess.check_call(['./apps/gradlew',
+      ':viewer:assemble' + android_variant + android_buildtype,
+      '-papps/' + args.app_name,
+      '-P' + target_cpu + 'out.dir=' + args.output_dir,
+      '--daemon'], cwd=os.path.join(os.path.dirname(__file__), ".."))
+except subprocess.CalledProcessError as error:
+  print error
+  sys.exit("gradle build failed")
+
+# copy apk back into the main out directory
+current_dir = os.path.dirname(__file__)
+apk_src = os.path.join(current_dir, "..", "apps", args.app_name, "build", "outputs", "apk",
+                       args.app_name + "-"  + android_variant + "-"  + android_buildtype + ".apk")
+apk_dst = os.path.join(args.output_dir, args.app_name + ".apk")
+shutil.copyfile(apk_src, apk_dst)
diff --git a/platform_tools/android/bin/android_gdb_native b/platform_tools/android/bin/android_gdb_native
index da513cb..9d7fa19 100755
--- a/platform_tools/android/bin/android_gdb_native
+++ b/platform_tools/android/bin/android_gdb_native
@@ -7,7 +7,7 @@
 source $SCRIPT_DIR/utils/android_setup.sh
 
 # setup the gdbserver
-$SCRIPT_DIR/android_gdbserver -d ${DEVICE_ID} ${APP_ARGS[@]}
+$SCRIPT_DIR/android_gdbserver -C ${SKIA_OUT} ${APP_ARGS[@]}
 
 # quit if gdbserver setup failed
 if [[ "$?" != "0" ]]; then
diff --git a/platform_tools/android/bin/android_gdbserver b/platform_tools/android/bin/android_gdbserver
index 432ab7c..184bd6a 100755
--- a/platform_tools/android/bin/android_gdbserver
+++ b/platform_tools/android/bin/android_gdbserver
@@ -15,6 +15,14 @@
   exit 1
 fi
 
+IS_64_BIT="false"
+GN_TARGET_CPU=$(grep target_cpu ${SKIA_OUT}/args.gn)
+if [ -z "$GN_TARGET_CPU"]; then
+  IS_64_BIT="true"
+elif [[ $GN_TARGET_CPU == *64* ]]; then
+  IS_64_BIT="true"
+fi
+
 # We need the debug symbols from these files
 GDB_TMP_DIR=$SKIA_OUT/android_gdb_tmp
 mkdir -p $GDB_TMP_DIR
@@ -47,7 +55,7 @@
     fi
 done
 
-if [[ $ANDROID_ARCH == *64* ]]; then
+if [[ $IS_64_BIT == "true" ]]; then
   adb_pull_if_needed /system/bin/linker64 $GDB_TMP_DIR
 else
   adb_pull_if_needed /system/bin/linker $GDB_TMP_DIR
@@ -58,7 +66,7 @@
 adb_push_if_needed "${TARGET_EXE}" /data/local/tmp
 
 echo "Pushing gdbserver..."
-adb_push_if_needed $GDBSERVER_DIR/gdbserver/gdbserver /data/local/tmp
+adb_push_if_needed $SKIA_OUT/gdbserver /data/local/tmp
 
 echo "Setting up port forward"
 $ADB forward "tcp:5039" "tcp:5039"
diff --git a/platform_tools/android/bin/android_install_app b/platform_tools/android/bin/android_install_app
index fe16cd5..0c9cd54 100755
--- a/platform_tools/android/bin/android_install_app
+++ b/platform_tools/android/bin/android_install_app
@@ -7,13 +7,12 @@
   echo " Options:         -f  Forces the package to be installed by removing any"
   echo "                      previously installed packages"
   echo "                  -h  Prints this help message"
-  echo "            --release Install the release build of Skia"
   echo "      -s [device_s/n] Serial number of the device to be used"
 }
 
 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 
-source $SCRIPT_DIR/android_setup.sh
+source $SCRIPT_DIR/utils/android_setup.sh
 source $SCRIPT_DIR/utils/setup_adb.sh
 
 forceRemoval="false"
@@ -24,8 +23,6 @@
   elif [[ "${arg}" == "-h" ]]; then
     print_usage
     exit
-  elif [[ "${arg}" == "-r" ]]; then
-    echo "DEPRECATED: -r is now a no-op"
   elif [[ ${arg} == '-'* ]]; then
     echo "ERROR: unrecognized option ${arg}"
     print_usage
@@ -33,22 +30,14 @@
   fi
 done
 
+APP_LC=$(echo Viewer | tr "[:upper:]" "[:lower:]")
 
 if [[ "$forceRemoval" == "true" ]];
 then
     echo "Forcing removal of previously installed packages"
-    $ADB ${DEVICE_SERIAL} uninstall com.skia > /dev/null
+    $ADB ${DEVICE_SERIAL} uninstall org.skia.${APP_LC} > /dev/null
 fi
 
-if [[ "$BUILDTYPE" == "Release" ]];
-then
-    apk_suffix="release.apk"
-else
-    apk_suffix="debug.apk"
-fi
-
-APP_LC=$(echo Viewer | tr "[:upper:]" "[:lower:]")
-
-echo "Installing ${APP_LC} from ${APP_LC}/build/outputs/apk/${APP_LC}-${ANDROID_ARCH}-${apk_suffix}"
-$ADB ${DEVICE_SERIAL} install -r ${SCRIPT_DIR}/../apps/${APP_LC}/build/outputs/apk/${APP_LC}-${ANDROID_ARCH}-${apk_suffix}
+echo "Installing ${APP_LC} from ${SKIA_OUT}/${APP_LC}.apk"
+$ADB ${DEVICE_SERIAL} install -r ${SKIA_OUT}/${APP_LC}.apk
 
diff --git a/platform_tools/android/bin/android_launch_app b/platform_tools/android/bin/android_launch_app
index 0e65ba1..0da80ba 100755
--- a/platform_tools/android/bin/android_launch_app
+++ b/platform_tools/android/bin/android_launch_app
@@ -3,13 +3,13 @@
 # android_launch_app: Launches the skia Viewer app on the device.
 
 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-source $SCRIPT_DIR/android_setup.sh
+source $SCRIPT_DIR/utils/android_setup.sh
 source $SCRIPT_DIR/utils/setup_adb.sh
 
 # TODO: check to ensure that the app exists on the device and prompt to install
 
 if [[ -n $RESOURCE_PATH ]]; then
-  adb_push_if_needed "${SKIA_SRC_DIR}/resources" $RESOURCE_PATH
+  adb_push_if_needed "${SCRIPT_DIR}/../../../resources" $RESOURCE_PATH
 fi
 
 activity="org.skia.viewer/org.skia.viewer.ViewerActivity"
diff --git a/platform_tools/android/bin/android_make b/platform_tools/android/bin/android_make
deleted file mode 100755
index cac0cc9..0000000
--- a/platform_tools/android/bin/android_make
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-# Fail-fast if anything in the script fails.
-set -e
-
-# Remove any existing .android_config file before running android_setup. If we
-# did not remove this now then we would build for whatever device type was
-# listed in the .android_config instead of the default device type.
-rm -f .android_config
-
-SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
-source $SCRIPT_DIR/utils/android_setup.sh
-
-SKIA_SRC_DIR=$(cd "${SCRIPT_DIR}/../../.."; pwd)
-echo $GN_ARGS
-gn gen $SKIA_OUT --args="${GN_ARGS}"
-ninja -C $SKIA_OUT ${APP_ARGS[@]}
-
-# Write the device id into the .android_config file.  This tells
-# android_run_skia the last build we completed.
-echo $DEVICE_ID > .android_config
-
diff --git a/platform_tools/android/bin/android_ninja b/platform_tools/android/bin/android_ninja
deleted file mode 120000
index 68a0fb1..0000000
--- a/platform_tools/android/bin/android_ninja
+++ /dev/null
@@ -1 +0,0 @@
-android_make
\ No newline at end of file
diff --git a/platform_tools/android/bin/android_perf b/platform_tools/android/bin/android_perf
index cf51074..0d1d5e2 100755
--- a/platform_tools/android/bin/android_perf
+++ b/platform_tools/android/bin/android_perf
@@ -12,15 +12,6 @@
 source $SCRIPT_DIR/utils/android_setup.sh
 source $SCRIPT_DIR/utils/setup_adb.sh
 
-if [ $(uname) == "Linux" ]; then
-    PERFHOST=$SCRIPT_DIR/linux/perfhost
-elif [ $(uname) == "Darwin" ]; then
-    PERFHOST=$SCRIPT_DIR/mac/perfhost
-else
-    echo "Could not automatically determine OS!"
-    exit 1;
-fi
-
 # grab and remove the perf command from the input args
 PERF_CMD=${APP_ARGS[0]}
 unset APP_ARGS[0]
@@ -42,7 +33,6 @@
     echo "Copying symbol files"
     adb_pull_if_needed /system/lib/libc.so $TMP_SYS_LIB
     adb_pull_if_needed /system/lib/libstdc++.so $TMP_SYS_LIB
-    adb_pull_if_needed /system/lib/libstlport.so $TMP_SYS_LIB
     adb_pull_if_needed /system/lib/libGLESv2.so $TMP_SYS_LIB
     adb_pull_if_needed /system/lib/libandroid.so $TMP_SYS_LIB
     adb_pull_if_needed /system/lib/libm.so $TMP_SYS_LIB
@@ -55,6 +45,9 @@
       exit 1
     fi
 
+    echo "Pushing simpleperf..."
+    adb_push_if_needed $SKIA_OUT/simpleperf /data/local/tmp
+
     echo "Pushing app..."
     adb_push_if_needed "${SKIA_OUT}/${runVars[0]}" /data/local/tmp
     cp "${SKIA_OUT}/${runVars[0]}" $TMP_APP_LOC
@@ -73,16 +66,16 @@
     # TO BE READ BY THE REPORTING TOOL
     echo "Starting profiler"
     APP_PID=$($ADB shell ps | grep ${runVars[0]} | awk '{print $2}')
-    $ADB shell perf record -p ${APP_PID} sleep 70
+    $ADB shell /data/local/tmp/simpleperf record -p ${APP_PID} -o /data/local/tmp/perf.data sleep 70
 
-    $ADB pull /data/perf.data $PERF_TMP_DIR/perf.data
+    $ADB pull /data/local/tmp/perf.data $PERF_TMP_DIR/perf.data
 
     exit 0;
 }
 
 perf_report() {
-    adb_pull_if_needed /data/perf.data $PERF_TMP_DIR/perf.data
-    $PERFHOST report -i $PERF_TMP_DIR/perf.data --symfs=$PERF_TMP_DIR ${runVars[@]}
+    adb_pull_if_needed /data/local/tmp/perf.data $PERF_TMP_DIR/perf.data
+    $SKIA_OUT/perfhost_report.py -i $PERF_TMP_DIR/perf.data --symfs=$PERF_TMP_DIR ${runVars[@]}
 }
 
 # Clean up
diff --git a/platform_tools/android/bin/android_run_skia b/platform_tools/android/bin/android_run_skia
index 659b690..5a643df 100755
--- a/platform_tools/android/bin/android_run_skia
+++ b/platform_tools/android/bin/android_run_skia
@@ -17,7 +17,7 @@
 adb_push_if_needed "${SKIA_OUT}/${APP_ARGS[0]}" /data/local/tmp
 if [[ -n $RESOURCE_PATH ]]; then
   verbose "pushing resources onto the device..."
-  adb_push_if_needed "${SKIA_SRC_DIR}/resources" $RESOURCE_PATH
+  adb_push_if_needed "${SCRIPT_DIR}/../../../resources" $RESOURCE_PATH
 fi
 
 if [ $LOGCAT ]; then
diff --git a/platform_tools/android/bin/utils/android_setup.sh b/platform_tools/android/bin/utils/android_setup.sh
index a50819e..4d6dba3 100755
--- a/platform_tools/android/bin/utils/android_setup.sh
+++ b/platform_tools/android/bin/utils/android_setup.sh
@@ -14,8 +14,8 @@
 IS_DEBUG="false"
 
 while (( "$#" )); do
-  if [[ "$1" == "-d" ]]; then
-    DEVICE_ID=$2
+  if [[ "$1" == "-C" ]]; then
+    SKIA_OUT=$2
     shift
   elif [[ "$1" == "-i" || "$1" == "--resourcePath" ]]; then
     RESOURCE_PATH=$2
@@ -24,14 +24,10 @@
   elif [[ "$1" == "-s" ]]; then
     DEVICE_SERIAL="-s $2"
     shift
-  elif [[ "$1" == "--debug" ]]; then
-    IS_DEBUG="true"
   elif [[ "$1" == "--logcat" ]]; then
     LOGCAT=1
   elif [[ "$1" == "--verbose" ]]; then
     VERBOSE="true"
-  elif [[ "$1" == "--vulkan" ]]; then
-    SKIA_VULKAN="true"
   else
     APP_ARGS=("${APP_ARGS[@]}" "${1}")
   fi
@@ -66,11 +62,6 @@
   fi
 fi
 
-if [ -z "$ANDROID_HOME" ]; then
-  echo "ANDROID_HOME not set so we are setting it to a default value of ANDROID_SDK_ROOT"
-  exportVar ANDROID_HOME $ANDROID_SDK_ROOT
-fi
-
 if [ -z "$ANDROID_NDK_ROOT" ]; then
   if [ -d "${ANDROID_SDK_ROOT}/ndk-bundle" ]; then
     exportVar ANDROID_NDK_ROOT ${ANDROID_SDK_ROOT}/ndk-bundle
@@ -80,77 +71,6 @@
   fi
 fi
 
-# Helper function to configure the GN defines to the appropriate values
-# based on the target device.
-setup_device() {
-  DEFINES="ndk=\"${ANDROID_NDK_ROOT}\" is_debug=${IS_DEBUG}"
-
-  if [ $SKIA_VULKAN == "true" ]; then
-    DEFINES="${DEFINES} ndk_api=24"
-  fi
-
-  # Setup the build variation depending on the target device
-  TARGET_DEVICE="$1"
-
-  if [ -z "$TARGET_DEVICE" ]; then
-    if [ -f .android_config ]; then
-      TARGET_DEVICE=$(cat .android_config)
-      verbose "no target device (-d), using ${TARGET_DEVICE} from most recent build"
-    else
-      TARGET_DEVICE="arm_v7"
-      verbose "no target device (-d), using ${TARGET_DEVICE}"
-    fi
-  fi
-
-  case $TARGET_DEVICE in
-    arm_v7 | nexus_4 | nexus_5 | nexus_6 | nexus_7 | nexus_10)
-      DEFINES="${DEFINES} target_cpu=\"arm\""
-      GDBSERVER_DIR="${ANDROID_NDK_ROOT}/prebuilt/android-arm"
-      IS_64_BIT=false
-      ;;
-    arm64 | nexus_9 | nexus_5x | nexus_6p | pixel)
-      DEFINES="${DEFINES} target_cpu=\"arm64\""
-      GDBSERVER_DIR="${ANDROID_NDK_ROOT}/prebuilt/android-arm64"
-      IS_64_BIT=true
-      ;;
-    x86)
-      DEFINES="${DEFINES} target_cpu=\"x86\""
-      GDBSERVER_DIR="${ANDROID_NDK_ROOT}/prebuilt/android-x86"
-      IS_64_BIT=false
-      ;;
-    x86_64 | x64)
-      DEFINES="${DEFINES} target_cpu=\"x64\""
-      GDBSERVER_DIR="${ANDROID_NDK_ROOT}/prebuilt/android-x86_64"
-      IS_64_BIT=true
-      ;;
-    mips)
-      DEFINES="${DEFINES} target_cpu=\"mipsel\""
-      GDBSERVER_DIR="${ANDROID_NDK_ROOT}/prebuilt/android-mips"
-      IS_64_BIT=false
-      #DEFINES="${DEFINES} skia_resource_cache_mb_limit=32"
-      ;;
-    mips64)
-      DEFINES="${DEFINES} target_cpu=\"mips64el\""
-      GDBSERVER_DIR="${ANDROID_NDK_ROOT}/prebuilt/android-mips64"
-      IS_64_BIT=true
-      ;;
-    *)
-      echo "ERROR: unknown device $TARGET_DEVICE"
-      exit 1
-      ;;
-  esac
-
-  verbose "The build is targeting the device: $TARGET_DEVICE"
-  exportVar DEVICE_ID $TARGET_DEVICE
-  exportVar GN_ARGS "$DEFINES"
-  exportVar GDBSERVER_DIR $GDBSERVER_DIR
-  exportVar IS_64_BIT $IS_64_BIT
-
-  SKIA_SRC_DIR=$(cd "${UTIL_DIR}/../../../.."; pwd)
-  DEFAULT_SKIA_OUT="${SKIA_SRC_DIR}/out/android-${TARGET_DEVICE}"
-  exportVar SKIA_OUT "${SKIA_OUT:-${DEFAULT_SKIA_OUT}}"
-}
-
 # adb_pull_if_needed(android_src, host_dst)
 adb_pull_if_needed() {
 
@@ -247,5 +167,3 @@
   # turn error checking back on
   set -e
 }
-
-setup_device "${DEVICE_ID}"
diff --git a/public.bzl b/public.bzl
index a87d325..844d320 100644
--- a/public.bzl
+++ b/public.bzl
@@ -108,6 +108,7 @@
         "src/images/*",
         "src/opts/**/*",
         "src/ports/**/*",
+        "src/splicer/*",
         "src/utils/android/**/*",
         "src/utils/mac/**/*",
         "src/utils/SkThreadUtils_win.cpp",  # Windows-only. Move to ports?
@@ -433,9 +434,7 @@
         "tools/timer/*.h",
     ],
     exclude = [
-        "dm/DMSrcSinkAndroid.cpp",  # Android-only.
         "tests/FontMgrAndroidParserTest.cpp",  # Android-only.
-        "tests/PathOpsSkpClipTest.cpp",  # Alternate main.
         "tests/skia_test.cpp",  # Old main.
         "tests/SkpSkGrTest.cpp",  # Alternate main.
         "tests/SVGDeviceTest.cpp",
@@ -459,8 +458,6 @@
 
 DM_SRCS_ANDROID = struct(
     include = [
-        # Depends on Android HWUI library that is not available in google3.
-        #"dm/DMSrcSinkAndroid.cpp",
         "tests/FontMgrAndroidParserTest.cpp",
         # TODO(benjaminwagner): Figure out how to compile with EGL.
         "tools/gpu/gl/CreatePlatformGLContext_none.cpp",
@@ -590,7 +587,6 @@
     # Turn on a few Google3-specific build fixes.
     "GOOGLE3",
     # Staging flags for API changes
-    "SK_SUPPORT_LEGACY_IMAGE_ENCODER_CLASS",
     # Temporarily Disable analytic AA for Google3
     "SK_NO_ANALYTIC_AA",
     "SK_SUPPORT_LEGACY_BITMAP_SETPIXELREF",
diff --git a/resources/randPixelsAnim.gif b/resources/randPixelsAnim.gif
new file mode 100644
index 0000000..7b12bfc
--- /dev/null
+++ b/resources/randPixelsAnim.gif
Binary files differ
diff --git a/samplecode/SampleBigGradient.cpp b/samplecode/SampleBigGradient.cpp
index 8ae0990..8bc1f59 100644
--- a/samplecode/SampleBigGradient.cpp
+++ b/samplecode/SampleBigGradient.cpp
@@ -1,4 +1,4 @@
-/*
+/*
  * Copyright 2011 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
@@ -9,6 +9,7 @@
 #include "SkView.h"
 #include "SkCanvas.h"
 #include "SkGradientShader.h"
+#include "SkMakeUnique.h"
 
 static sk_sp<SkShader> make_grad(SkScalar w, SkScalar h) {
     SkColor colors[] = { 0xFF000000, 0xFF333333 };
@@ -45,3 +46,260 @@
 
 static SkView* MyFactory() { return new BigGradientView; }
 static SkViewRegister reg(MyFactory);
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkRasterHandleAllocator.h"
+
+class GraphicsPort {
+protected:
+    SkCanvas* fCanvas;
+
+public:
+    GraphicsPort(SkCanvas* canvas) : fCanvas(canvas) {}
+    virtual ~GraphicsPort() {}
+
+    void save() { fCanvas->save(); }
+    void saveLayer(const SkRect& bounds, SkAlpha alpha) {
+        fCanvas->saveLayerAlpha(&bounds, alpha);
+    }
+    void restore() { fCanvas->restore(); }
+
+    void translate(float x, float y) { fCanvas->translate(x, y); }
+    void scale(float s) { fCanvas->scale(s, s); }
+
+    void drawOval(const SkRect& r, SkColor c) {
+        SkPaint p;
+        p.setColor(c);
+        fCanvas->drawOval(r, p);
+    }
+
+    virtual void drawRect(const SkRect& r, SkColor c) {
+        SkPaint p;
+        p.setColor(c);
+        fCanvas->drawRect(r, p);
+    }
+};
+
+#ifdef SK_BUILD_FOR_MAC
+
+#include "SkCGUtils.h"
+class CGGraphicsPort : public GraphicsPort {
+public:
+    CGGraphicsPort(SkCanvas* canvas) : GraphicsPort(canvas) {}
+
+    void drawRect(const SkRect& r, SkColor c) override {
+        CGContextRef cg = (CGContextRef)fCanvas->accessTopRasterHandle();
+        
+        CGColorRef color = CGColorCreateGenericRGB(SkColorGetR(c)/255.f,
+                                                   SkColorGetG(c)/255.f,
+                                                   SkColorGetB(c)/255.f,
+                                                   SkColorGetA(c)/255.f);
+
+        CGContextSetFillColorWithColor(cg, color);
+        CGContextFillRect(cg, CGRectMake(r.x(), r.y(), r.width(), r.height()));
+    }
+};
+
+static CGAffineTransform matrix_to_transform(CGContextRef cg, const SkMatrix& ctm) {
+    SkMatrix matrix;
+    matrix.setScale(1, -1);
+    matrix.postTranslate(0, SkIntToScalar(CGBitmapContextGetHeight(cg)));
+    matrix.preConcat(ctm);
+
+    return CGAffineTransformMake(matrix[SkMatrix::kMScaleX],
+                                 matrix[SkMatrix::kMSkewY],
+                                 matrix[SkMatrix::kMSkewX],
+                                 matrix[SkMatrix::kMScaleY],
+                                 matrix[SkMatrix::kMTransX],
+                                 matrix[SkMatrix::kMTransY]);
+}
+
+class Allocator_CG : public SkRasterHandleAllocator {
+public:
+    Allocator_CG() {}
+    
+    bool allocHandle(const SkImageInfo& info, Rec* rec) override {
+        // let CG allocate the pixels
+        CGContextRef cg = SkCreateCGContext(SkPixmap(info, nullptr, 0));
+        if (!cg) {
+            return false;
+        }
+        rec->fReleaseProc = [](void* pixels, void* ctx){ CGContextRelease((CGContextRef)ctx); };
+        rec->fReleaseCtx = cg;
+        rec->fPixels = CGBitmapContextGetData(cg);
+        rec->fRowBytes = CGBitmapContextGetBytesPerRow(cg);
+        rec->fHandle = cg;
+        CGContextSaveGState(cg);    // balanced each time updateContext is called
+        return true;
+    }
+
+    void updateHandle(Handle hndl, const SkMatrix& ctm, const SkIRect& clip) override {
+        CGContextRef cg = (CGContextRef)hndl;
+        
+        CGContextRestoreGState(cg);
+        CGContextSaveGState(cg);
+        CGContextClearRect(cg, CGRectMake(clip.x(), clip.y(), clip.width(), clip.height()));
+        CGContextConcatCTM(cg, matrix_to_transform(cg, ctm));
+    }
+};
+
+#define MyPort CGGraphicsPort
+#define MyAllocator Allocator_CG
+
+#elif defined(WIN32)
+
+class GDIGraphicsPort : public GraphicsPort {
+public:
+    GDIGraphicsPort(SkCanvas* canvas) : GraphicsPort(canvas) {}
+
+    void drawRect(const SkRect& r, SkColor c) override {
+        HDC hdc = (HDC)fCanvas->accessTopRasterHandle();
+
+        COLORREF cr = RGB(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));// SkEndian_Swap32(c) >> 8;
+        SkIRect ir = r.round();
+        RECT rect = { ir.left(), ir.top(), ir.right(), ir.bottom() };
+        FillRect(hdc, &rect, CreateSolidBrush(cr));
+
+        // Assuming GDI wrote zeros for alpha, this will or-in 0xFF for alpha
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kDstATop);
+        fCanvas->drawRect(r, paint);
+    }
+};
+
+static void DeleteHDCCallback(void*, void* context) {
+    HDC hdc = static_cast<HDC>(context);
+    HBITMAP hbitmap = static_cast<HBITMAP>(SelectObject(hdc, nullptr));
+    DeleteObject(hbitmap);
+    DeleteDC(hdc);
+}
+
+// We use this static factory function instead of the regular constructor so
+// that we can create the pixel data before calling the constructor. This is
+// required so that we can call the base class' constructor with the pixel
+// data.
+static bool Create(int width, int height, bool is_opaque, SkRasterHandleAllocator::Rec* rec) {
+    BITMAPINFOHEADER hdr = { 0 };
+    hdr.biSize = sizeof(BITMAPINFOHEADER);
+    hdr.biWidth = width;
+    hdr.biHeight = -height;  // Minus means top-down bitmap.
+    hdr.biPlanes = 1;
+    hdr.biBitCount = 32;
+    hdr.biCompression = BI_RGB;  // No compression.
+    hdr.biSizeImage = 0;
+    hdr.biXPelsPerMeter = 1;
+    hdr.biYPelsPerMeter = 1;
+    void* pixels;
+    HBITMAP hbitmap = CreateDIBSection(nullptr, (const BITMAPINFO*)&hdr, 0, &pixels, 0, 0);
+    if (!hbitmap) {
+        return false;
+    }
+
+    size_t row_bytes = width * sizeof(SkPMColor);
+    sk_bzero(pixels, row_bytes * height);
+
+    HDC hdc = CreateCompatibleDC(nullptr);
+    if (!hdc) {
+        DeleteObject(hbitmap);
+        return false;
+    }
+    SetGraphicsMode(hdc, GM_ADVANCED);
+    SelectObject(hdc, hbitmap);
+
+    rec->fReleaseProc = DeleteHDCCallback;
+    rec->fReleaseCtx = hdc;
+    rec->fPixels = pixels;
+    rec->fRowBytes = row_bytes;
+    rec->fHandle = hdc;
+    return true;
+}
+
+/**
+*  Subclass of SkRasterHandleAllocator that returns an HDC as its "handle".
+*/
+class GDIAllocator : public SkRasterHandleAllocator {
+public:
+    GDIAllocator() {}
+
+    bool allocHandle(const SkImageInfo& info, Rec* rec) override {
+        SkASSERT(info.colorType() == kN32_SkColorType);
+        return Create(info.width(), info.height(), info.isOpaque(), rec);
+    }
+
+    void updateHandle(Handle handle, const SkMatrix& ctm, const SkIRect& clip_bounds) override {
+        HDC hdc = static_cast<HDC>(handle);
+
+        XFORM xf;
+        xf.eM11 = ctm[SkMatrix::kMScaleX];
+        xf.eM21 = ctm[SkMatrix::kMSkewX];
+        xf.eDx = ctm[SkMatrix::kMTransX];
+        xf.eM12 = ctm[SkMatrix::kMSkewY];
+        xf.eM22 = ctm[SkMatrix::kMScaleY];
+        xf.eDy = ctm[SkMatrix::kMTransY];
+        SetWorldTransform(hdc, &xf);
+
+#if 0
+        HRGN hrgn = CreateRectRgnIndirect(&skia::SkIRectToRECT(clip_bounds));
+        int result = SelectClipRgn(hdc, hrgn);
+        SkASSERT(result != ERROR);
+        result = DeleteObject(hrgn);
+        SkASSERT(result != 0);
+#endif
+    }
+};
+
+#define MyPort GDIGraphicsPort
+#define MyAllocator GDIAllocator
+
+#endif
+
+#ifdef MyAllocator
+class RasterAllocatorSample : public SampleView {
+public:
+    RasterAllocatorSample() {}
+
+protected:
+    bool onQuery(SkEvent* evt) override {
+        if (SampleCode::TitleQ(*evt)) {
+            SampleCode::TitleR(evt, "raster-allocator");
+            return true;
+        }
+        return this->INHERITED::onQuery(evt);
+    }
+
+    void doDraw(GraphicsPort* port) {
+        port->drawRect({0, 0, 256, 256}, SK_ColorRED);
+        port->save();
+        port->translate(30, 30);
+        port->drawRect({0, 0, 30, 30}, SK_ColorBLUE);
+        port->drawOval({10, 10, 20, 20}, SK_ColorWHITE);
+        port->restore();
+        
+        port->saveLayer({50, 50, 100, 100}, 0x80);
+        port->drawRect({55, 55, 95, 95}, SK_ColorGREEN);
+        port->restore();
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        GraphicsPort skp(canvas);
+        doDraw(&skp);
+
+        const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256);
+        std::unique_ptr<SkCanvas> c2 =
+            SkRasterHandleAllocator::MakeCanvas(skstd::make_unique<MyAllocator>(), info);
+        MyPort cgp(c2.get());
+        doDraw(&cgp);
+
+        SkPixmap pm;
+        c2->peekPixels(&pm);
+        SkBitmap bm;
+        bm.installPixels(pm);
+        canvas->drawBitmap(bm, 280, 0, nullptr);
+    }
+
+private:
+    typedef SampleView INHERITED;
+};
+DEF_SAMPLE( return new RasterAllocatorSample; )
+#endif
diff --git a/site/dev/sheriffing/android.md b/site/dev/sheriffing/android.md
index e3c2f37..2845d29 100644
--- a/site/dev/sheriffing/android.md
+++ b/site/dev/sheriffing/android.md
@@ -4,6 +4,7 @@
 ### Contents ###
 
 *   [What does a Android RoboCop do?](#what_is_a_robocop)
+*   [Android Autoroller](#autoroller_doc)
 *   [View current and upcoming RoboCops](#view_current_upcoming_robocops)
 *   [How to swap RoboCop shifts](#how_to_swap)
 
@@ -14,13 +15,31 @@
 
 The RoboCop has two primary jobs:
 
-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.
+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@).
 
 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.
 
 
+<a name="autoroller_doc"></a>
+Android Autoroller
+------------------
+
+The Android autoroller runs on the [client.skia.internal](https://chromegw.corp.google.com/i/client.skia.internal/console) master using the [merge_into_android.py](https://chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave/+/master/skia/merge_into_android.py) recipe.
+
+If you need to stop the autoroller then do the following steps:
+
+* echo "stop" > /tmp/action
+* gsutil cp /tmp/action gs://skia-android-autoroller/action
+
+To turn the autoroller back on:
+
+* echo "start" > /tmp/action
+* gsutil cp /tmp/action gs://skia-android-autoroller/action
+
+If you need any more information about the autoroller please look at [skia:6065](https://bugs.chromium.org/p/skia/issues/detail?id=6065) or ask rmistry@ / skiabot@.
+
 <a name="view_current_upcoming_robocops"></a>
 View current and upcoming RoboCops
 ----------------------------------
diff --git a/site/dev/sheriffing/index.md b/site/dev/sheriffing/index.md
index d2352df..af3fda7 100644
--- a/site/dev/sheriffing/index.md
+++ b/site/dev/sheriffing/index.md
@@ -120,7 +120,7 @@
 <a name="tree_closers"></a>
 ### Compile bot failures automatically close the tree
 
-A failure of the build steps in all compile bots automatically closes the tree. Sheriffs will have to manually reopen the tree manually when they deem the problem fixed.
+A failure of the build steps in all compile bots automatically closes the tree. Sheriffs will have to manually reopen the tree when they deem the problem fixed.
 
 Note: The tree is not closed automatically if the last run of the failed compile builder had the same failing step. The tree is also not closed if the tree was automatically closed less than 10 mins ago. If the tree is already closed then no action is taken.
 
@@ -149,7 +149,7 @@
   * Description:
       * DEPS roll #,
       * Helpful message about what went wrong (e.g., “Changes to how lighting is scaled in Skia r#### changed the following images:”)
-      * Layout tests effected
+      * Layout tests affected
       * You should copy the list of affected from stdio of the failing bot
   * Status: Assigned
   * Owner: yourself
diff --git a/site/user/sample/pdf.md b/site/user/sample/pdf.md
index d54172b..7bd7b74 100644
--- a/site/user/sample/pdf.md
+++ b/site/user/sample/pdf.md
@@ -8,36 +8,33 @@
 
     #include "SkDocument.h"
 
-    bool WritePDF(SkWStream* outputStream) {
+    void WritePDF(SkWStream* outputStream,
+                  const char* documentTitle,
+                  void (*writePage)(SkCanvas*, int page),
+                  int numberOfPages,
+                  SkSize pageSize) {
         SkDocument::PDFMetadata metadata;
-        metadata.fCreator  = "creator....";
-        metadata.fTitle    = "title...";
-        metadata.fAuthor   = "author...";
-        metadata.fSubject  = "subject...";
-        metadata.fKeywords = "keywords...";
-        metadata.fCreator  = "creator...";
-        SkTime::DateTime now = get_current_date_and_time();
-        metadata.fCreation.fEnabled = true;
+        metadata.fTitle = documentTitle;
+        metadata.fCreator = "Example WritePDF() Function";
+        SkTime::DateTime now;
+        SkTime::GetDateTime(&now);
+        metadata.fCreation.fEnabled  = true;
         metadata.fCreation.fDateTime = now;
-        metadata.fModified.fEnabled = true;
+        metadata.fModified.fEnabled  = true;
         metadata.fModified.fDateTime = now;
-        sk_sp<SkDocument> pdfDocument(SkDocument::MakePDF(
+        sk_sp<SkDocument> pdfDocument = SkDocument::MakePDF(
                 outputStream, SK_ScalarDefaultRasterDPI, metadata,
                 nullptr, true);
         assert(pdfDocument);
 
-        int numberOfPages = ....;
         for (int page = 0; page < numberOfPages; ++page) {
-            SkScalar pageWidth = ....;
-            SkScalar pageHeight = ....;
             SkCanvas* pageCanvas =
-                    pdfDocument->beginPage(pageWidth, pageHeight);
-
-            // ....insert canvas draw commands here....
-
+                    pdfDocument->beginPage(pageSize.width(),
+                                           pageSize.height());
+            writePage(pageCanvas, page);
             pdfDocument->endPage();
         }
-        return pdfDocument->close();
+        pdfDocument->close();
     }
 
 * * *
diff --git a/site/user/sample/viewer.md b/site/user/sample/viewer.md
index 758858d..d41590a 100644
--- a/site/user/sample/viewer.md
+++ b/site/user/sample/viewer.md
@@ -22,7 +22,13 @@
 
 Android
 -------
-GN support for the Android Viewer is in the process of being addressed.
+The Viewer APK must be built by gradle which can be invoked on the command line with the following script...
+
+    ./platform_tools/android/bin/android_build_app -C <out_dir> viewer
+
+*   **out_dir** is the ninja out directory that you want to use to build the app
+
+Upon completion of the script the APK can be found at <out_dir>/viewer.apk
 
 iOS
 ---
diff --git a/src/codec/SkCodecImageGenerator.cpp b/src/codec/SkCodecImageGenerator.cpp
index fcbd7c3..0758878 100644
--- a/src/codec/SkCodecImageGenerator.cpp
+++ b/src/codec/SkCodecImageGenerator.cpp
@@ -61,26 +61,13 @@
 }
 
 bool SkCodecImageGenerator::onGenerateScaledPixels(const SkPixmap& pixmap) {
-    SkPMColor colorStorage[256];
-    int colorCount = 256;
-    const auto result = fCodec->getPixels(pixmap.info(), pixmap.writable_addr(),
-                                          pixmap.rowBytes(), nullptr, colorStorage, &colorCount);
-    switch (result) {
-        case SkCodec::kSuccess:
-        case SkCodec::kIncompleteInput:
-            break;
-        default:
-            return false;
+    if (pixmap.colorType() == kIndex_8_SkColorType) {
+        // There is no way to tell the client about the color table with this API.
+        return false;
     }
 
-    if (pixmap.colorType() == kIndex_8_SkColorType) {
-        // SkPixmap does not take ownership, so we need to hang onto this.
-        // FIXME: With a better API on SkCodec, the SkCodec could share its SkColorTable.
-        fColorTable.reset(new SkColorTable(colorStorage, colorCount));
-        const_cast<SkPixmap&>(pixmap).reset(pixmap.info(), pixmap.addr(), pixmap.rowBytes(),
-                                            fColorTable.get());
-    }
-    return true;
+    return this->onGetPixels(pixmap.info(), pixmap.writable_addr(), pixmap.rowBytes(),
+                             nullptr, nullptr);
 }
 
 
diff --git a/src/codec/SkCodecPriv.h b/src/codec/SkCodecPriv.h
index 75fbcf2..110cbdc 100644
--- a/src/codec/SkCodecPriv.h
+++ b/src/codec/SkCodecPriv.h
@@ -10,6 +10,7 @@
 
 #include "SkColorPriv.h"
 #include "SkColorSpaceXform.h"
+#include "SkColorSpaceXformPriv.h"
 #include "SkColorTable.h"
 #include "SkEncodedInfo.h"
 #include "SkImageInfo.h"
@@ -115,26 +116,6 @@
      return nullptr != colorTable ? colorTable->readColors() : nullptr;
 }
 
-static inline SkColorSpaceXform::ColorFormat select_xform_format(SkColorType colorType) {
-    switch (colorType) {
-        case kRGBA_8888_SkColorType:
-            return SkColorSpaceXform::kRGBA_8888_ColorFormat;
-        case kBGRA_8888_SkColorType:
-            return SkColorSpaceXform::kBGRA_8888_ColorFormat;
-        case kRGBA_F16_SkColorType:
-            return SkColorSpaceXform::kRGBA_F16_ColorFormat;
-        case kIndex_8_SkColorType:
-#ifdef SK_PMCOLOR_IS_RGBA
-            return SkColorSpaceXform::kRGBA_8888_ColorFormat;
-#else
-            return SkColorSpaceXform::kBGRA_8888_ColorFormat;
-#endif
-        default:
-            SkASSERT(false);
-            return SkColorSpaceXform::kRGBA_8888_ColorFormat;
-    }
-}
-
 /*
  * Given that the encoded image uses a color table, return the fill value
  */
diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp
index 5699580..ebd0b74 100644
--- a/src/codec/SkPngCodec.cpp
+++ b/src/codec/SkPngCodec.cpp
@@ -9,6 +9,7 @@
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
 #include "SkColorSpace.h"
+#include "SkColorSpacePriv.h"
 #include "SkColorTable.h"
 #include "SkMath.h"
 #include "SkOpts.h"
@@ -317,12 +318,6 @@
     return 1.0f / png_fixed_point_to_float(x);
 }
 
-static constexpr float gSRGB_toXYZD50[] {
-    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
-};
-
 #endif // LIBPNG >= 1.6
 
 // Returns a colorSpace object that represents any color space information in
diff --git a/src/core/SkAAClip.h b/src/core/SkAAClip.h
index 7b29ef1..c94756f 100644
--- a/src/core/SkAAClip.h
+++ b/src/core/SkAAClip.h
@@ -8,6 +8,7 @@
 #ifndef SkAAClip_DEFINED
 #define SkAAClip_DEFINED
 
+#include "SkAutoMalloc.h"
 #include "SkBlitter.h"
 #include "SkRegion.h"
 
diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp
index 422faa5..e7c75b2 100644
--- a/src/core/SkBitmap.cpp
+++ b/src/core/SkBitmap.cpp
@@ -713,6 +713,25 @@
     return src.pixmap().readPixels(requestedDstInfo, dstPixels, dstRB, x, y);
 }
 
+bool SkBitmap::readPixels(const SkPixmap& dst, int srcX, int srcY) const {
+    return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), srcX, srcY);
+}
+
+bool SkBitmap::writePixels(const SkPixmap& src, int dstX, int dstY) {
+    SkAutoPixmapUnlock dst;
+    if (!this->requestLock(&dst)) {
+        return false;
+    }
+
+    SkPixmap subset;
+    if (!dst.pixmap().extractSubset(&subset,
+                                    SkIRect::MakeXYWH(dstX, dstY, src.width(), src.height()))) {
+        return false;
+    }
+
+    return src.readPixels(subset);
+}
+
 bool SkBitmap::copyTo(SkBitmap* dst, SkColorType dstColorType, Allocator* alloc) const {
     if (!this->canCopyTo(dstColorType)) {
         return false;
diff --git a/src/core/SkBitmapDevice.cpp b/src/core/SkBitmapDevice.cpp
index a5dadbb..6d63ecf 100644
--- a/src/core/SkBitmapDevice.cpp
+++ b/src/core/SkBitmapDevice.cpp
@@ -17,6 +17,7 @@
 #include "SkPixelRef.h"
 #include "SkPixmap.h"
 #include "SkRasterClip.h"
+#include "SkRasterHandleAllocator.h"
 #include "SkShader.h"
 #include "SkSpecialImage.h"
 #include "SkSurface.h"
@@ -79,21 +80,25 @@
     return Create(info, SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType));
 }
 
-SkBitmapDevice::SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps)
+SkBitmapDevice::SkBitmapDevice(const SkBitmap& bitmap, const SkSurfaceProps& surfaceProps,
+                               SkRasterHandleAllocator::Handle hndl)
     : INHERITED(bitmap.info(), surfaceProps)
     , fBitmap(bitmap)
+    , fRasterHandle(hndl)
 {
     SkASSERT(valid_for_bitmap_device(bitmap.info(), nullptr));
     fBitmap.lockPixels();
 }
 
 SkBitmapDevice* SkBitmapDevice::Create(const SkImageInfo& origInfo,
-                                       const SkSurfaceProps& surfaceProps) {
+                                       const SkSurfaceProps& surfaceProps,
+                                       SkRasterHandleAllocator* allocator) {
     SkAlphaType newAT = origInfo.alphaType();
     if (!valid_for_bitmap_device(origInfo, &newAT)) {
         return nullptr;
     }
 
+    SkRasterHandleAllocator::Handle hndl = nullptr;
     const SkImageInfo info = origInfo.makeAlphaType(newAT);
     SkBitmap bitmap;
 
@@ -101,6 +106,11 @@
         if (!bitmap.setInfo(info)) {
             return nullptr;
         }
+    } else if (allocator) {
+        hndl = allocator->allocBitmap(info, &bitmap);
+        if (!hndl) {
+            return nullptr;
+        }
     } else if (info.isOpaque()) {
         // If this bitmap is opaque, we don't have any sensible default color,
         // so we just return uninitialized pixels.
@@ -116,7 +126,7 @@
         }
     }
 
-    return new SkBitmapDevice(bitmap, surfaceProps);
+    return new SkBitmapDevice(bitmap, surfaceProps, hndl);
 }
 
 void SkBitmapDevice::setNewSize(const SkISize& size) {
@@ -135,7 +145,7 @@
 
 SkBaseDevice* SkBitmapDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint*) {
     const SkSurfaceProps surfaceProps(this->surfaceProps().flags(), cinfo.fPixelGeometry);
-    return SkBitmapDevice::Create(cinfo.fInfo, surfaceProps);
+    return SkBitmapDevice::Create(cinfo.fInfo, surfaceProps, cinfo.fAllocator);
 }
 
 const SkBitmap& SkBitmapDevice::onAccessBitmap() {
@@ -167,12 +177,7 @@
         return false;
     }
 
-    const SkImageInfo dstInfo = fBitmap.info().makeWH(srcInfo.width(), srcInfo.height());
-
-    void* dstPixels = fBitmap.getAddr(x, y);
-    size_t dstRowBytes = fBitmap.rowBytes();
-
-    if (SkPixelInfo::CopyPixels(dstInfo, dstPixels, dstRowBytes, srcInfo, srcPixels, srcRowBytes)) {
+    if (fBitmap.writePixels(SkPixmap(srcInfo, srcPixels, srcRowBytes), x, y)) {
         fBitmap.notifyPixelsChanged();
         return true;
     }
diff --git a/src/core/SkBitmapProcState_matrix.h b/src/core/SkBitmapProcState_matrix.h
index e0180c6..ea784c6 100644
--- a/src/core/SkBitmapProcState_matrix.h
+++ b/src/core/SkBitmapProcState_matrix.h
@@ -70,9 +70,10 @@
     }
 
 #ifdef CHECK_FOR_DECAL
-    if (can_truncate_to_fixed_for_decal(fx, dx, count, maxX)) {
-        decal_filter_scale(xy, SkFractionalIntToFixed(fx),
-                           SkFractionalIntToFixed(dx), count);
+    const SkFixed fixedFx = SkFractionalIntToFixed(fx);
+    const SkFixed fixedDx = SkFractionalIntToFixed(dx);
+    if (can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) {
+        decal_filter_scale(xy, fixedFx, fixedDx, count);
     } else
 #endif
     {
diff --git a/src/core/SkBitmapProcState_matrix_template.h b/src/core/SkBitmapProcState_matrix_template.h
index 0c93718..c38610a 100644
--- a/src/core/SkBitmapProcState_matrix_template.h
+++ b/src/core/SkBitmapProcState_matrix_template.h
@@ -36,32 +36,37 @@
 
     const SkFractionalInt dx = s.fInvSxFractionalInt;
 
-    if (tryDecal && can_truncate_to_fixed_for_decal(fx, dx, count, maxX)) {
-        decal_nofilter_scale(xy, SkFractionalIntToFixed(fx),
-                             SkFractionalIntToFixed(dx), count);
-    } else {
-        int i;
-        for (i = (count >> 2); i > 0; --i) {
-            unsigned a, b;
-            a = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
-            b = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
-#ifdef SK_CPU_BENDIAN
-            *xy++ = (a << 16) | b;
-#else
-            *xy++ = (b << 16) | a;
-#endif
-            a = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
-            b = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
-#ifdef SK_CPU_BENDIAN
-            *xy++ = (a << 16) | b;
-#else
-            *xy++ = (b << 16) | a;
-#endif
+    if (tryDecal) {
+        const SkFixed fixedFx = SkFractionalIntToFixed(fx);
+        const SkFixed fixedDx = SkFractionalIntToFixed(dx);
+
+        if (can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) {
+            decal_nofilter_scale(xy, fixedFx, fixedDx, count);
+            return;
         }
-        uint16_t* xx = (uint16_t*)xy;
-        for (i = (count & 3); i > 0; --i) {
-            *xx++ = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
-        }
+    }
+
+    int i;
+    for (i = (count >> 2); i > 0; --i) {
+        unsigned a, b;
+        a = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
+        b = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
+#ifdef SK_CPU_BENDIAN
+        *xy++ = (a << 16) | b;
+#else
+        *xy++ = (b << 16) | a;
+#endif
+        a = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
+        b = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
+#ifdef SK_CPU_BENDIAN
+        *xy++ = (a << 16) | b;
+#else
+        *xy++ = (b << 16) | a;
+#endif
+    }
+    uint16_t* xx = (uint16_t*)xy;
+    for (i = (count & 3); i > 0; --i) {
+        *xx++ = TileProc::X(s, SkFractionalIntToFixed(fx), maxX); fx += dx;
     }
 }
 
diff --git a/src/core/SkBitmapProcState_utils.h b/src/core/SkBitmapProcState_utils.h
index 3c4c1fa..4609ff3 100644
--- a/src/core/SkBitmapProcState_utils.h
+++ b/src/core/SkBitmapProcState_utils.h
@@ -1,10 +1,17 @@
+/*
+ * 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
 
 // 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 unsigned SK_USHIFT16(unsigned x) {
+static inline unsigned SK_USHIFT16(unsigned x) {
     return x >> 16;
 }
 
@@ -18,10 +25,10 @@
  *  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(SkFractionalInt frX,
-                                                   SkFractionalInt frDx,
+static inline bool can_truncate_to_fixed_for_decal(SkFixed fx,
+                                                   SkFixed dx,
                                                    int count, unsigned max) {
-    SkFixed dx = SkFractionalIntToFixed(frDx);
+    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
@@ -30,11 +37,20 @@
         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!
-    SkFixed fx = SkFractionalIntToFixed(frX);
-    return (unsigned)SkFixedFloorToInt(fx) <= max &&
-           (unsigned)SkFixedFloorToInt(fx + dx * (count - 1)) < max;
+    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 sk_64_isS32(lastFx) && (unsigned)SkFixedFloorToInt(sk_64_asS32(lastFx)) < max;
 }
 
 #endif /* #ifndef SkBitmapProcState_utils_DEFINED */
diff --git a/src/core/SkBlendModePriv.h b/src/core/SkBlendModePriv.h
index 29df639..0d0589c 100644
--- a/src/core/SkBlendModePriv.h
+++ b/src/core/SkBlendModePriv.h
@@ -17,7 +17,7 @@
 
 #if SK_SUPPORT_GPU
 #include "GrXferProcessor.h"
-sk_sp<GrXPFactory> SkBlendMode_AsXPFactory(SkBlendMode);
+const GrXPFactory* SkBlendMode_AsXPFactory(SkBlendMode);
 #endif
 
 
diff --git a/include/core/SkBlitRow.h b/src/core/SkBlitRow.h
similarity index 100%
rename from include/core/SkBlitRow.h
rename to src/core/SkBlitRow.h
diff --git a/src/core/SkBlitter.h b/src/core/SkBlitter.h
index f03a308..fd6ca8c 100644
--- a/src/core/SkBlitter.h
+++ b/src/core/SkBlitter.h
@@ -8,12 +8,12 @@
 #ifndef SkBlitter_DEFINED
 #define SkBlitter_DEFINED
 
+#include "SkAutoMalloc.h"
 #include "SkBitmapProcShader.h"
 #include "SkColor.h"
 #include "SkRect.h"
 #include "SkRegion.h"
 #include "SkShader.h"
-#include "SkTypes.h"
 
 class SkMatrix;
 class SkPaint;
diff --git a/src/core/SkBlurImageFilter.cpp b/src/core/SkBlurImageFilter.cpp
index 9ec3303..92a5d0f 100644
--- a/src/core/SkBlurImageFilter.cpp
+++ b/src/core/SkBlurImageFilter.cpp
@@ -136,6 +136,11 @@
 #if SK_SUPPORT_GPU
     if (source->isTextureBacked()) {
         GrContext* context = source->getContext();
+
+        // Ensure the input is in the destination's gamut. This saves us from having to do the
+        // xform during the filter itself.
+        input = ImageToColorSpace(input.get(), ctx.outputProperties());
+
         sk_sp<GrTexture> inputTexture(input->asTextureRef(context));
         if (!inputTexture) {
             return nullptr;
@@ -152,13 +157,14 @@
         offset->fY = dstBounds.fTop;
         inputBounds.offset(-inputOffset);
         dstBounds.offset(-inputOffset);
-        // We intentionally use the source's color space, not the destination's (from ctx). We
-        // always blur in the source's config, so we need a compatible color space. We also want to
-        // avoid doing gamut conversion on every fetch of the texture.
+        // Typically, we would create the RTC with the output's color space (from ctx), but we
+        // always blur in the PixelConfig of the *input*. Those might not be compatible (if they
+        // have different transfer functions). We've already guaranteed that those color spaces
+        // have the same gamut, so in this case, we do everything in the input's color space.
         sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
                                                                 context,
                                                                 inputTexture.get(),
-                                                                sk_ref_sp(source->getColorSpace()),
+                                                                sk_ref_sp(input->getColorSpace()),
                                                                 dstBounds,
                                                                 &inputBounds,
                                                                 sigma.x(),
@@ -167,11 +173,11 @@
             return nullptr;
         }
 
-        // TODO: Get the colorSpace from the renderTargetContext (once it has one)
         return SkSpecialImage::MakeFromGpu(SkIRect::MakeWH(dstBounds.width(), dstBounds.height()),
                                            kNeedNewImageUniqueID_SpecialImage,
                                            renderTargetContext->asTexture(),
-                                           sk_ref_sp(input->getColorSpace()), &source->props());
+                                           sk_ref_sp(renderTargetContext->getColorSpace()),
+                                           &source->props());
     }
 #endif
 
diff --git a/src/core/SkBuffer.cpp b/src/core/SkBuffer.cpp
index df8dc69..7fd4170 100644
--- a/src/core/SkBuffer.cpp
+++ b/src/core/SkBuffer.cpp
@@ -9,56 +9,49 @@
 
 #include <string.h>
 
-////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
-void SkRBuffer::readNoSizeCheck(void* buffer, size_t size)
-{
-    SkASSERT((fData != 0 && fStop == 0) || fPos + size <= fStop);
-    if (buffer)
-        memcpy(buffer, fPos, size);
-    fPos += size;
-}
-
-const void* SkRBuffer::skip(size_t size)
-{
-    const void* result = fPos;
-    readNoSizeCheck(nullptr, size);
-    return result;
-}
-
-size_t SkRBuffer::skipToAlign4()
-{
-    size_t pos = this->pos();
-    size_t n = SkAlign4(pos) - pos;
-    fPos += n;
-    return n;
-}
-
-bool SkRBufferWithSizeCheck::read(void* buffer, size_t size) {
-    fError = fError || (size > static_cast<size_t>(fStop - fPos));
-    if (!fError && (size > 0)) {
-        readNoSizeCheck(buffer, size);
+bool SkRBuffer::read(void* buffer, size_t size) {
+    if (fValid && size <= this->available()) {
+        if (buffer) {
+            memcpy(buffer, fPos, size);
+        }
+        fPos += size;
+        return true;
+    } else {
+        fValid = false;
+        return false;
     }
-    return !fError;
 }
 
-void* SkWBuffer::skip(size_t size)
-{
+bool SkRBuffer::skipToAlign4() {
+    intptr_t pos = reinterpret_cast<intptr_t>(fPos);
+    size_t n = SkAlign4(pos) - pos;
+    if (fValid && n <= this->available()) {
+        fPos += n;
+        return true;
+    } else {
+        fValid = false;
+        return false;
+    }
+}
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+void* SkWBuffer::skip(size_t size) {
     void* result = fPos;
     writeNoSizeCheck(nullptr, size);
     return fData == nullptr ? nullptr : result;
 }
 
-void SkWBuffer::writeNoSizeCheck(const void* buffer, size_t size)
-{
+void SkWBuffer::writeNoSizeCheck(const void* buffer, size_t size) {
     SkASSERT(fData == 0 || fStop == 0 || fPos + size <= fStop);
     if (fData && buffer)
         memcpy(fPos, buffer, size);
     fPos += size;
 }
 
-size_t SkWBuffer::padToAlign4()
-{
+size_t SkWBuffer::padToAlign4() {
     size_t pos = this->pos();
     size_t n = SkAlign4(pos) - pos;
 
@@ -85,53 +78,4 @@
     #define AssertBuffer32(buffer)
 #endif
 
-void* sk_buffer_write_int32(void* buffer, int32_t value)
-{
-    AssertBuffer32(buffer);
-    *(int32_t*)buffer = value;
-    return (char*)buffer + sizeof(int32_t);
-}
-
-void* sk_buffer_write_int32(void* buffer, const int32_t values[], int count)
-{
-    AssertBuffer32(buffer);
-    SkASSERT(count >= 0);
-
-    memcpy((int32_t*)buffer, values, count * sizeof(int32_t));
-    return (char*)buffer + count * sizeof(int32_t);
-}
-
-const void* sk_buffer_read_int32(const void* buffer, int32_t* value)
-{
-    AssertBuffer32(buffer);
-    if (value)
-        *value = *(const int32_t*)buffer;
-    return (const char*)buffer + sizeof(int32_t);
-}
-
-const void* sk_buffer_read_int32(const void* buffer, int32_t values[], int count)
-{
-    AssertBuffer32(buffer);
-    SkASSERT(count >= 0);
-
-    if (values)
-        memcpy(values, (const int32_t*)buffer, count * sizeof(int32_t));
-    return (const char*)buffer + count * sizeof(int32_t);
-}
-
-void* sk_buffer_write_ptr(void* buffer, void* ptr)
-{
-    AssertBuffer32(buffer);
-    *(void**)buffer = ptr;
-    return (char*)buffer + sizeof(void*);
-}
-
-const void* sk_buffer_read_ptr(const void* buffer, void** ptr)
-{
-    AssertBuffer32(buffer);
-    if (ptr)
-        *ptr = *(void**)buffer;
-    return (const char*)buffer + sizeof(void*);
-}
-
 #endif
diff --git a/src/core/SkBuffer.h b/src/core/SkBuffer.h
index c466fb6..c9c1a8e 100644
--- a/src/core/SkBuffer.h
+++ b/src/core/SkBuffer.h
@@ -22,14 +22,7 @@
 class SkRBuffer : SkNoncopyable {
 public:
     SkRBuffer() : fData(0), fPos(0), fStop(0) {}
-    /** Initialize RBuffer with a data pointer, but no specified length.
-        This signals the RBuffer to not perform range checks during reading.
-    */
-    SkRBuffer(const void* data) {
-        fData = (const char*)data;
-        fPos = (const char*)data;
-        fStop = 0;  // no bounds checking
-    }
+
     /** Initialize RBuffer with a data point and length.
     */
     SkRBuffer(const void* data, size_t size) {
@@ -39,79 +32,39 @@
         fStop = (const char*)data + size;
     }
 
-    virtual ~SkRBuffer() { }
-
     /** Return the number of bytes that have been read from the beginning
         of the data pointer.
     */
-    size_t  pos() const { return fPos - fData; }
+    size_t pos() const { return fPos - fData; }
     /** Return the total size of the data pointer. Only defined if the length was
         specified in the constructor or in a call to reset().
     */
-    size_t  size() const { return fStop - fData; }
+    size_t size() const { return fStop - fData; }
     /** Return true if the buffer has read to the end of the data pointer.
         Only defined if the length was specified in the constructor or in a call
         to reset(). Always returns true if the length was not specified.
     */
-    bool    eof() const { return fPos >= fStop; }
+    bool eof() const { return fPos >= fStop; }
+
+    size_t available() const { return fStop - fPos; }
+
+    bool isValid() const { return fValid; }
 
     /** Read the specified number of bytes from the data pointer. If buffer is not
         null, copy those bytes into buffer.
     */
-    virtual bool read(void* buffer, size_t size) {
-        if (size) {
-            this->readNoSizeCheck(buffer, size);
-        }
-        return true;
-    }
+    bool read(void* buffer, size_t size);
+    bool skipToAlign4();
 
-    const void* skip(size_t size); // return start of skipped data
-    size_t  skipToAlign4();
+    bool readU8(uint8_t* x)   { return this->read(x, 1); }
+    bool readS32(int32_t* x)  { return this->read(x, 4); }
+    bool readU32(uint32_t* x) { return this->read(x, 4); }
 
-    bool readPtr(void** ptr) { return read(ptr, sizeof(void*)); }
-    bool readScalar(SkScalar* x) { return read(x, 4); }
-    bool readU32(uint32_t* x) { return read(x, 4); }
-    bool readS32(int32_t* x) { return read(x, 4); }
-    bool readU16(uint16_t* x) { return read(x, 2); }
-    bool readS16(int16_t* x) { return read(x, 2); }
-    bool readU8(uint8_t* x) { return read(x, 1); }
-    bool readBool(bool* x) {
-        uint8_t u8;
-        if (this->readU8(&u8)) {
-            *x = (u8 != 0);
-            return true;
-        }
-        return false;
-    }
-
-protected:
-    void    readNoSizeCheck(void* buffer, size_t size);
-
+private:
     const char* fData;
     const char* fPos;
     const char* fStop;
-};
-
-/** \class SkRBufferWithSizeCheck
-
-    Same as SkRBuffer, except that a size check is performed before the read operation and an
-    error is set if the read operation is attempting to read past the end of the data.
-*/
-class SkRBufferWithSizeCheck : public SkRBuffer {
-public:
-    SkRBufferWithSizeCheck(const void* data, size_t size) : SkRBuffer(data, size), fError(false) {}
-
-    /** Read the specified number of bytes from the data pointer. If buffer is not
-        null and the number of bytes to read does not overflow this object's data,
-        copy those bytes into buffer.
-    */
-    bool read(void* buffer, size_t size) override;
-
-    /** Returns whether or not a read operation attempted to read past the end of the data.
-    */
-    bool isValid() const { return !fError; }
-private:
-    bool fError;
+    bool        fValid = true;
 };
 
 /** \class SkWBuffer
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index cb76051..4716053 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -29,6 +29,7 @@
 #include "SkPicture.h"
 #include "SkRadialShadowMapShader.h"
 #include "SkRasterClip.h"
+#include "SkRasterHandleAllocator.h"
 #include "SkReadPixelsRec.h"
 #include "SkRRect.h"
 #include "SkShadowPaintFilterCanvas.h"
@@ -772,17 +773,21 @@
     this->init(device.get(), kDefault_InitFlags);
 }
 
-SkCanvas::SkCanvas(const SkBitmap& bitmap)
+SkCanvas::SkCanvas(const SkBitmap& bitmap, std::unique_ptr<SkRasterHandleAllocator> alloc,
+                   SkRasterHandleAllocator::Handle hndl)
     : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
     , fProps(SkSurfaceProps::kLegacyFontHost_InitType)
+    , fAllocator(std::move(alloc))
     , fConservativeRasterClip(false)
 {
     inc_canvas();
 
-    sk_sp<SkBaseDevice> device(new SkBitmapDevice(bitmap, fProps));
+    sk_sp<SkBaseDevice> device(new SkBitmapDevice(bitmap, fProps, hndl));
     this->init(device.get(), kDefault_InitFlags);
 }
 
+SkCanvas::SkCanvas(const SkBitmap& bitmap) : SkCanvas(bitmap, nullptr, nullptr) {}
+
 SkCanvas::~SkCanvas() {
     // free up the contents of our deque
     this->restoreToCount(1);    // restore everything but the last
@@ -1260,7 +1265,8 @@
                                      (saveLayerFlags & kPreserveLCDText_SaveLayerFlag);
         const SkBaseDevice::TileUsage usage = SkBaseDevice::kNever_TileUsage;
         const SkBaseDevice::CreateInfo createInfo = SkBaseDevice::CreateInfo(info, usage, geo,
-                                                                             preserveLCDText);
+                                                                             preserveLCDText,
+                                                                             fAllocator.get());
         newDevice.reset(priorDevice->onCreateDevice(createInfo, paint));
         if (!newDevice) {
             return;
@@ -3404,3 +3410,58 @@
 static_assert((int)SkRegion::kXOR_Op                == (int)kXOR_SkClipOp, "");
 static_assert((int)SkRegion::kReverseDifference_Op  == (int)kReverseDifference_SkClipOp, "");
 static_assert((int)SkRegion::kReplace_Op            == (int)kReplace_SkClipOp, "");
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+SkRasterHandleAllocator::Handle SkCanvas::accessTopRasterHandle() const {
+    if (fAllocator && fMCRec->fTopLayer->fDevice) {
+        const SkBaseDevice* dev = fMCRec->fTopLayer->fDevice;
+        SkRasterHandleAllocator::Handle handle = dev->getRasterHandle();
+        SkIPoint origin = dev->getOrigin();
+        SkMatrix ctm = this->getTotalMatrix();
+        ctm.preTranslate(SkIntToScalar(-origin.x()), SkIntToScalar(-origin.y()));
+
+        SkIRect clip = fMCRec->fRasterClip.getBounds();
+        clip.offset(-origin.x(), -origin.y());
+        if (clip.intersect(0, 0, dev->width(), dev->height())) {
+            clip.setEmpty();
+        }
+
+        fAllocator->updateHandle(handle, ctm, clip);
+        return handle;
+    }
+    return nullptr;
+}
+
+static bool install(SkBitmap* bm, const SkImageInfo& info,
+                    const SkRasterHandleAllocator::Rec& rec) {
+    return bm->installPixels(info, rec.fPixels, rec.fRowBytes, nullptr,
+                             rec.fReleaseProc, rec.fReleaseCtx);
+}
+
+SkRasterHandleAllocator::Handle SkRasterHandleAllocator::allocBitmap(const SkImageInfo& info,
+                                                                     SkBitmap* bm) {
+    SkRasterHandleAllocator::Rec rec;
+    if (!this->allocHandle(info, &rec) || !install(bm, info, rec)) {
+        return nullptr;
+    }
+    return rec.fHandle;
+}
+
+std::unique_ptr<SkCanvas>
+SkRasterHandleAllocator::MakeCanvas(std::unique_ptr<SkRasterHandleAllocator> alloc,
+                                    const SkImageInfo& info, const Rec* rec) {
+    if (!alloc || !supported_for_raster_canvas(info)) {
+        return nullptr;
+    }
+
+    SkBitmap bm;
+    Handle hndl;
+
+    if (rec) {
+        hndl = install(&bm, info, *rec) ? rec->fHandle : nullptr;
+    } else {
+        hndl = alloc->allocBitmap(info, &bm);
+    }
+    return hndl ? std::unique_ptr<SkCanvas>(new SkCanvas(bm, std::move(alloc), hndl)) : nullptr;
+}
diff --git a/src/core/SkColor.cpp b/src/core/SkColor.cpp
index 6dacc06..bee114b 100644
--- a/src/core/SkColor.cpp
+++ b/src/core/SkColor.cpp
@@ -73,30 +73,32 @@
 SkColor SkHSVToColor(U8CPU a, const SkScalar hsv[3]) {
     SkASSERT(hsv);
 
-    U8CPU s = SkUnitScalarClampToByte(hsv[1]);
-    U8CPU v = SkUnitScalarClampToByte(hsv[2]);
+    SkScalar s = SkScalarPin(hsv[1], 0, 1);
+    SkScalar v = SkScalarPin(hsv[2], 0, 1);
 
-    if (0 == s) { // shade of gray
-        return SkColorSetARGB(a, v, v, v);
+    U8CPU v_byte = SkScalarRoundToInt(v * 255);
+
+    if (SkScalarNearlyZero(s)) { // shade of gray
+        return SkColorSetARGB(a, v_byte, v_byte, v_byte);
     }
-    SkFixed hx = (hsv[0] < 0 || hsv[0] >= SkIntToScalar(360)) ? 0 : SkScalarToFixed(hsv[0]/60);
-    SkFixed f = hx & 0xFFFF;
+    SkScalar hx = (hsv[0] < 0 || hsv[0] >= SkIntToScalar(360)) ? 0 : hsv[0]/60;
+    SkScalar w = SkScalarFloorToScalar(hx);
+    SkScalar f = hx - w;
 
-    unsigned v_scale = SkAlpha255To256(v);
-    unsigned p = SkAlphaMul(255 - s, v_scale);
-    unsigned q = SkAlphaMul(255 - (s * f >> 16), v_scale);
-    unsigned t = SkAlphaMul(255 - (s * (SK_Fixed1 - f) >> 16), v_scale);
+    unsigned p = SkScalarRoundToInt((SK_Scalar1 - s) * v * 255);
+    unsigned q = SkScalarRoundToInt((SK_Scalar1 - (s * f)) * v * 255);
+    unsigned t = SkScalarRoundToInt((SK_Scalar1 - (s * (SK_Scalar1 - f))) * v * 255);
 
     unsigned r, g, b;
 
-    SkASSERT((unsigned)(hx >> 16) < 6);
-    switch (hx >> 16) {
-        case 0: r = v; g = t; b = p; break;
-        case 1: r = q; g = v; b = p; break;
-        case 2: r = p; g = v; b = t; break;
-        case 3: r = p; g = q; b = v; break;
-        case 4: r = t;  g = p; b = v; break;
-        default: r = v; g = p; b = q; break;
+    SkASSERT((unsigned)(w) < 6);
+    switch ((unsigned)(w)) {
+        case 0: r = v_byte;  g = t;      b = p; break;
+        case 1: r = q;       g = v_byte; b = p; break;
+        case 2: r = p;       g = v_byte; b = t; break;
+        case 3: r = p;       g = q;      b = v_byte; break;
+        case 4: r = t;       g = p;      b = v_byte; break;
+        default: r = v_byte; g = p;      b = q; break;
     }
     return SkColorSetARGB(a, r, g, b);
 }
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index f6cbf0a..239a61b 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -165,6 +165,10 @@
         return SkColorSpace_Base::MakeRGB(k2Dot2Curve_SkGammaNamed, toXYZD50);
     }
 
+    if (is_almost_linear(coeffs)) {
+        return SkColorSpace_Base::MakeRGB(kLinear_SkGammaNamed, toXYZD50);
+    }
+
     void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
     sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
     SkColorSpaceTransferFn* fn = SkTAddOffset<SkColorSpaceTransferFn>(memory, sizeof(SkGammas));
diff --git a/src/core/SkColorSpacePriv.h b/src/core/SkColorSpacePriv.h
index 5488939..5b09036 100644
--- a/src/core/SkColorSpacePriv.h
+++ b/src/core/SkColorSpacePriv.h
@@ -11,19 +11,6 @@
 
 #define SkColorSpacePrintf(...)
 
-#if defined(SK_USE_LEGACY_D50_MATRICES)
-static constexpr float gSRGB_toXYZD50[] {
-    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 gAdobeRGB_toXYZD50[] {
-    0.6098f, 0.2052f, 0.1492f,    // Rx, Gx, Bx
-    0.3111f, 0.6257f, 0.0632f,    // Ry, Gy, By
-    0.0195f, 0.0609f, 0.7448f,    // Rz, Gz, Bz
-};
-#else
 static constexpr float gSRGB_toXYZD50[] {
     0.4360747f, 0.3850649f, 0.1430804f, // Rx, Gx, Bx
     0.2225045f, 0.7168786f, 0.0606169f, // Ry, Gy, Gz
@@ -35,7 +22,6 @@
     0.3111242f, 0.6256560f, 0.0632197f, // Ry, Gy, Gz
     0.0194811f, 0.0608902f, 0.7448387f, // Rz, Gz, Bz
 };
-#endif
 
 static inline bool color_space_almost_equal(float a, float b) {
     return SkTAbs(a - b) < 0.01f;
@@ -65,7 +51,7 @@
     }
 
     if (coeffs.fD == 0.0f) {
-        // Y = (aX + b)^g + c  for always
+        // Y = (aX + b)^g + e  for always
         if (0.0f == coeffs.fA || 0.0f == coeffs.fG) {
             SkColorSpacePrintf("A or G is zero, constant transfer function "
                                "is nonsense");
@@ -74,16 +60,16 @@
     }
 
     if (coeffs.fD >= 1.0f) {
-        // Y = eX + f          for always
-        if (0.0f == coeffs.fE) {
-            SkColorSpacePrintf("E is zero, constant transfer function is "
+        // Y = cX + f          for always
+        if (0.0f == coeffs.fC) {
+            SkColorSpacePrintf("C is zero, constant transfer function is "
                                "nonsense");
             return false;
         }
     }
 
     if ((0.0f == coeffs.fA || 0.0f == coeffs.fG) && 0.0f == coeffs.fC) {
-        SkColorSpacePrintf("A or G, and E are zero, constant transfer function "
+        SkColorSpacePrintf("A or G, and C are zero, constant transfer function "
                            "is nonsense");
         return false;
     }
@@ -114,11 +100,27 @@
 static inline bool is_almost_2dot2(const SkColorSpaceTransferFn& coeffs) {
     return color_space_almost_equal(1.0f, coeffs.fA) &&
            color_space_almost_equal(0.0f, coeffs.fB) &&
-           color_space_almost_equal(0.0f, coeffs.fC) &&
-           color_space_almost_equal(0.0f, coeffs.fD) &&
            color_space_almost_equal(0.0f, coeffs.fE) &&
+           color_space_almost_equal(2.2f, coeffs.fG) &&
+           coeffs.fD <= 0.0f;
+}
+
+static inline bool is_almost_linear(const SkColorSpaceTransferFn& coeffs) {
+    // OutputVal = InputVal ^ 1.0f
+    const bool linearExp =
+           color_space_almost_equal(1.0f, coeffs.fA) &&
+           color_space_almost_equal(0.0f, coeffs.fB) &&
+           color_space_almost_equal(0.0f, coeffs.fE) &&
+           color_space_almost_equal(1.0f, coeffs.fG) &&
+           coeffs.fD <= 0.0f;
+
+    // OutputVal = 1.0f * InputVal
+    const bool linearFn =
+           color_space_almost_equal(1.0f, coeffs.fC) &&
            color_space_almost_equal(0.0f, coeffs.fF) &&
-           color_space_almost_equal(2.2f, coeffs.fG);
+           coeffs.fD >= 1.0f;
+
+    return linearExp || linearFn;
 }
 
 static inline void value_to_parametric(SkColorSpaceTransferFn* coeffs, float exponent) {
diff --git a/src/core/SkColorSpaceXform.cpp b/src/core/SkColorSpaceXform.cpp
index 6eaab83..b73a3cf 100644
--- a/src/core/SkColorSpaceXform.cpp
+++ b/src/core/SkColorSpaceXform.cpp
@@ -19,12 +19,6 @@
 #include "SkRasterPipeline.h"
 #include "SkSRGB.h"
 
-#if 0
-static constexpr bool kUseRasterPipeline = true;
-#else
-static constexpr bool kUseRasterPipeline = false;
-#endif
-
 static constexpr float sk_linear_from_2dot2[256] = {
         0.000000000000000000f, 0.000005077051900662f, 0.000023328004666099f, 0.000056921765712193f,
         0.000107187362341244f, 0.000175123977503027f, 0.000261543754548491f, 0.000367136269815943f,
@@ -331,27 +325,6 @@
         }
     }
 
-    if (kUseRasterPipeline) {
-        SrcGamma srcGamma = srcSpaceXYZ->gammaIsLinear() ? kLinear_SrcGamma : kTable_SrcGamma;
-        DstGamma dstGamma;
-        switch (dstSpaceXYZ->gammaNamed()) {
-            case kSRGB_SkGammaNamed:
-                dstGamma = kSRGB_DstGamma;
-                break;
-            case k2Dot2Curve_SkGammaNamed:
-                dstGamma = k2Dot2_DstGamma;
-                break;
-            case kLinear_SkGammaNamed:
-                dstGamma = kLinear_DstGamma;
-                break;
-            default:
-                dstGamma = kTable_DstGamma;
-                break;
-        }
-        return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_Pipeline(
-                srcSpaceXYZ, srcToDst, dstSpaceXYZ, csm, srcGamma, dstGamma));
-    }
-
     switch (csm) {
         case kNone_ColorSpaceMatch:
             switch (dstSpaceXYZ->gammaNamed()) {
@@ -468,12 +441,12 @@
 
 #define AI SK_ALWAYS_INLINE
 
-static AI void load_matrix(const float matrix[16],
+static AI void load_matrix(const float matrix[13],
                            Sk4f& rXgXbX, Sk4f& rYgYbY, Sk4f& rZgZbZ, Sk4f& rTgTbT) {
-    rXgXbX = Sk4f::Load(matrix +  0);
-    rYgYbY = Sk4f::Load(matrix +  4);
-    rZgZbZ = Sk4f::Load(matrix +  8);
-    rTgTbT = Sk4f::Load(matrix + 12);
+    rXgXbX = Sk4f::Load(matrix + 0);
+    rYgYbY = Sk4f::Load(matrix + 3);
+    rZgZbZ = Sk4f::Load(matrix + 6);
+    rTgTbT = Sk4f::Load(matrix + 9);
 }
 
 enum Order {
@@ -786,20 +759,6 @@
 }
 
 template <Order kOrder>
-static AI void store_f32(void* dst, const uint32_t* src, Sk4f& dr, Sk4f& dg, Sk4f& db, Sk4f& da,
-                         const uint8_t* const[3]) {
-    Sk4f::Store4(dst, dr, dg, db, da);
-}
-
-template <Order kOrder>
-static AI void store_f32_1(void* dst, const uint32_t* src,
-                           Sk4f& rgba, const Sk4f& a,
-                           const uint8_t* const[3]) {
-    rgba = Sk4f(rgba[0], rgba[1], rgba[2], a[3]);
-    rgba.store((float*) dst);
-}
-
-template <Order kOrder>
 static AI void store_f16_opaque(void* dst, const uint32_t* src, Sk4f& dr, Sk4f& dg, Sk4f& db,
                                 Sk4f&, const uint8_t* const[3]) {
     Sk4h::Store4(dst, SkFloatToHalf_finite_ftz(dr),
@@ -814,6 +773,7 @@
                                   const uint8_t* const[3]) {
     uint64_t tmp;
     SkFloatToHalf_finite_ftz(rgba).store(&tmp);
+    tmp &= 0x0000FFFFFFFFFFFF;
     tmp |= static_cast<uint64_t>(SK_Half1) << 48;
     *((uint64_t*) dst) = tmp;
 }
@@ -890,7 +850,6 @@
     kBGRA_8888_2Dot2_DstFormat,
     kBGRA_8888_Table_DstFormat,
     kF16_Linear_DstFormat,
-    kF32_Linear_DstFormat,
 };
 
 template <SrcFormat kSrc,
@@ -898,13 +857,12 @@
           SkAlphaType kAlphaType,
           ColorSpaceMatch kCSM>
 static void color_xform_RGBA(void* dst, const void* vsrc, int len,
-                             const float* const srcTables[3], const float matrix[16],
+                             const float* const srcTables[3], const float matrix[13],
                              const uint8_t* const dstTables[3]) {
     LoadFn load;
     Load1Fn load_1;
     const bool kLoadAlpha = (kPremul_SkAlphaType == kAlphaType) ||
-                            (kF16_Linear_DstFormat == kDst) ||
-                            (kF32_Linear_DstFormat == kDst);
+                            (kF16_Linear_DstFormat == kDst && kOpaque_SkAlphaType != kAlphaType);
     switch (kSrc) {
         case kRGBA_8888_Linear_SrcFormat:
             if (kLoadAlpha) {
@@ -995,11 +953,6 @@
                                                             store_f16_1<kRGBA_Order>;
             sizeOfDstPixel = 8;
             break;
-        case kF32_Linear_DstFormat:
-            store   = store_f32<kRGBA_Order>;
-            store_1 = store_f32_1<kRGBA_Order>;
-            sizeOfDstPixel = 16;
-            break;
     }
 
     // We always clamp before converting to 8888 outputs (because we have to).
@@ -1117,7 +1070,19 @@
 ::SkColorSpaceXform_XYZ(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst,
                         SkColorSpace_XYZ* dstSpace)
 {
-    srcToDst.asColMajorf(fSrcToDst);
+    fSrcToDst[ 0] = srcToDst.get(0, 0);
+    fSrcToDst[ 1] = srcToDst.get(1, 0);
+    fSrcToDst[ 2] = srcToDst.get(2, 0);
+    fSrcToDst[ 3] = srcToDst.get(0, 1);
+    fSrcToDst[ 4] = srcToDst.get(1, 1);
+    fSrcToDst[ 5] = srcToDst.get(2, 1);
+    fSrcToDst[ 6] = srcToDst.get(0, 2);
+    fSrcToDst[ 7] = srcToDst.get(1, 2);
+    fSrcToDst[ 8] = srcToDst.get(2, 2);
+    fSrcToDst[ 9] = srcToDst.get(0, 3);
+    fSrcToDst[10] = srcToDst.get(1, 3);
+    fSrcToDst[11] = srcToDst.get(2, 3);
+    fSrcToDst[12] = 0.0f;
 
     const int numSrcTables = num_tables(srcSpace);
     const size_t srcEntries = numSrcTables * 256;
@@ -1134,7 +1099,7 @@
 
 template <SrcFormat kSrc, DstFormat kDst, ColorSpaceMatch kCSM>
 static AI bool apply_set_alpha(void* dst, const void* src, int len, SkAlphaType alphaType,
-                               const float* const srcTables[3], const float matrix[16],
+                               const float* const srcTables[3], const float matrix[13],
                                const uint8_t* const dstTables[3]) {
     switch (alphaType) {
         case kOpaque_SkAlphaType:
@@ -1156,7 +1121,7 @@
 
 template <SrcGamma kSrc, DstFormat kDst, ColorSpaceMatch kCSM>
 static AI bool apply_set_src(void* dst, const void* src, int len, SkAlphaType alphaType,
-                             const float* const srcTables[3], const float matrix[16],
+                             const float* const srcTables[3], const float matrix[13],
                              const uint8_t* const dstTables[3],
                              SkColorSpaceXform::ColorFormat srcColorFormat) {
     switch (srcColorFormat) {
@@ -1211,6 +1176,10 @@
         }
     }
 
+    if (kRGBA_F32_ColorFormat == dstColorFormat) {
+        return this->applyPipeline(dstColorFormat, dst, srcColorFormat, src, len, alphaType);
+    }
+
     switch (dstColorFormat) {
         case kRGBA_8888_ColorFormat:
             switch (kDst) {
@@ -1259,16 +1228,8 @@
                 default:
                     return false;
             }
-        case kRGBA_F32_ColorFormat:
-            switch (kDst) {
-                case kLinear_DstGamma:
-                    return apply_set_src<kSrc, kF32_Linear_DstFormat, kCSM>
-                            (dst, src, len, alphaType, fSrcGammaTables, fSrcToDst, nullptr,
-                             srcColorFormat);
-                default:
-                    return false;
-            }
         default:
+            SkASSERT(false);
             return false;
     }
 }
@@ -1281,93 +1242,44 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-SkColorSpaceXform_Pipeline::SkColorSpaceXform_Pipeline(SkColorSpace_XYZ* srcSpace,
-                                                       const SkMatrix44& srcToDst,
-                                                       SkColorSpace_XYZ* dstSpace,
-                                                       ColorSpaceMatch csm,
-                                                       SrcGamma srcGamma,
-                                                       DstGamma dstGamma)
-    : fCSM(csm)
-    , fSrcGamma(srcGamma)
-    , fDstGamma(dstGamma)
-{
-    fSrcToDst[ 0] = srcToDst.get(0, 0);
-    fSrcToDst[ 1] = srcToDst.get(1, 0);
-    fSrcToDst[ 2] = srcToDst.get(2, 0);
-    fSrcToDst[ 3] = srcToDst.get(0, 1);
-    fSrcToDst[ 4] = srcToDst.get(1, 1);
-    fSrcToDst[ 5] = srcToDst.get(2, 1);
-    fSrcToDst[ 6] = srcToDst.get(0, 2);
-    fSrcToDst[ 7] = srcToDst.get(1, 2);
-    fSrcToDst[ 8] = srcToDst.get(2, 2);
-    fSrcToDst[ 9] = srcToDst.get(0, 3);
-    fSrcToDst[10] = srcToDst.get(1, 3);
-    fSrcToDst[11] = srcToDst.get(2, 3);
-
-    const int numSrcTables = num_tables(srcSpace);
-    const size_t srcEntries = numSrcTables * 256;
-    const bool srcGammasAreMatching = (1 >= numSrcTables);
-    fSrcStorage.reset(srcEntries);
-    build_gamma_tables(fSrcGammaTables, fSrcStorage.get(), 256, srcSpace, kToLinear,
-                       srcGammasAreMatching);
-
-    const int numDstTables = num_tables(dstSpace);
-    dstSpace->toDstGammaTables(fDstGammaTables, &fDstStorage, numDstTables);
-}
-
-bool SkColorSpaceXform_Pipeline::onApply(ColorFormat dstColorFormat, void* dst,
-                                         ColorFormat srcColorFormat, const void* src, int len,
-                                         SkAlphaType alphaType) const {
-    if (kFull_ColorSpaceMatch == fCSM) {
-        if (kPremul_SkAlphaType != alphaType) {
-            if ((kRGBA_8888_ColorFormat == dstColorFormat &&
-                 kRGBA_8888_ColorFormat == srcColorFormat) ||
-                (kBGRA_8888_ColorFormat == dstColorFormat &&
-                 kBGRA_8888_ColorFormat == srcColorFormat))
-            {
-                memcpy(dst, src, len * sizeof(uint32_t));
-                return true;
-            }
-
-            if ((kRGBA_8888_ColorFormat == dstColorFormat &&
-                 kBGRA_8888_ColorFormat == srcColorFormat) ||
-                (kBGRA_8888_ColorFormat == dstColorFormat &&
-                 kRGBA_8888_ColorFormat == srcColorFormat))
-            {
-                SkOpts::RGBA_to_BGRA((uint32_t*) dst, src, len);
-                return true;
-            }
-        }
-    }
-
-    if (kRGBA_F16_ColorFormat == srcColorFormat || kRGBA_F32_ColorFormat == srcColorFormat) {
-        return false;
-    }
-
+template <SrcGamma kSrc, DstGamma kDst, ColorSpaceMatch kCSM>
+bool SkColorSpaceXform_XYZ<kSrc, kDst, kCSM>
+::applyPipeline(ColorFormat dstColorFormat, void* dst, ColorFormat srcColorFormat,
+                const void* src, int len, SkAlphaType alphaType) const {
     SkRasterPipeline pipeline;
 
     LoadTablesContext loadTables;
-    if (kLinear_SrcGamma == fSrcGamma) {
-        pipeline.append(SkRasterPipeline::load_8888, &src);
-        if (kBGRA_8888_ColorFormat == srcColorFormat) {
+    switch (srcColorFormat) {
+        case kRGBA_8888_ColorFormat:
+            if (kLinear_SrcGamma == kSrc) {
+                pipeline.append(SkRasterPipeline::load_8888, &src);
+            } else {
+                loadTables.fSrc = (const uint32_t*) src;
+                loadTables.fR = fSrcGammaTables[0];
+                loadTables.fG = fSrcGammaTables[1];
+                loadTables.fB = fSrcGammaTables[2];
+                pipeline.append(SkRasterPipeline::load_tables, &loadTables);
+            }
+
+            break;
+        case kBGRA_8888_ColorFormat:
+            if (kLinear_SrcGamma == kSrc) {
+                pipeline.append(SkRasterPipeline::load_8888, &src);
+            } else {
+                loadTables.fSrc = (const uint32_t*) src;
+                loadTables.fR = fSrcGammaTables[2];
+                loadTables.fG = fSrcGammaTables[1];
+                loadTables.fB = fSrcGammaTables[0];
+                pipeline.append(SkRasterPipeline::load_tables, &loadTables);
+            }
+
             pipeline.append(SkRasterPipeline::swap_rb);
-        }
-    } else {
-        loadTables.fSrc = (const uint32_t*) src;
-        loadTables.fG = fSrcGammaTables[1];
-        if (kRGBA_8888_ColorFormat == srcColorFormat) {
-            loadTables.fR = fSrcGammaTables[0];
-            loadTables.fB = fSrcGammaTables[2];
-            pipeline.append(SkRasterPipeline::load_tables, &loadTables);
-        } else {
-            loadTables.fR = fSrcGammaTables[2];
-            loadTables.fB = fSrcGammaTables[0];
-            pipeline.append(SkRasterPipeline::load_tables, &loadTables);
-            pipeline.append(SkRasterPipeline::swap_rb);
-        }
+            break;
+        default:
+            return false;
     }
 
-    if (kNone_ColorSpaceMatch == fCSM) {
+    if (kNone_ColorSpaceMatch == kCSM) {
         pipeline.append(SkRasterPipeline::matrix_3x4, fSrcToDst);
 
         if (kRGBA_8888_ColorFormat == dstColorFormat || kBGRA_8888_ColorFormat == dstColorFormat) {
@@ -1384,7 +1296,7 @@
     }
 
     StoreTablesContext storeTables;
-    switch (fDstGamma) {
+    switch (kDst) {
         case kSRGB_DstGamma:
             pipeline.append(SkRasterPipeline::to_srgb);
             break;
@@ -1397,7 +1309,7 @@
 
     switch (dstColorFormat) {
         case kRGBA_8888_ColorFormat:
-            if (kTable_DstGamma == fDstGamma) {
+            if (kTable_DstGamma == kDst) {
                 storeTables.fDst = (uint32_t*) dst;
                 storeTables.fR = fDstGammaTables[0];
                 storeTables.fG = fDstGammaTables[1];
@@ -1409,7 +1321,7 @@
             }
             break;
         case kBGRA_8888_ColorFormat:
-            if (kTable_DstGamma == fDstGamma) {
+            if (kTable_DstGamma == kDst) {
                 storeTables.fDst = (uint32_t*) dst;
                 storeTables.fR = fDstGammaTables[2];
                 storeTables.fG = fDstGammaTables[1];
@@ -1423,13 +1335,13 @@
             }
             break;
         case kRGBA_F16_ColorFormat:
-            if (kLinear_DstGamma != fDstGamma) {
+            if (kLinear_DstGamma != kDst) {
                 return false;
             }
             pipeline.append(SkRasterPipeline::store_f16, &dst);
             break;
         case kRGBA_F32_ColorFormat:
-            if (kLinear_DstGamma != fDstGamma) {
+            if (kLinear_DstGamma != kDst) {
                 return false;
             }
             pipeline.append(SkRasterPipeline::store_f32, &dst);
@@ -1443,13 +1355,7 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space) {
-    if (kUseRasterPipeline) {
-        return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_Pipeline(
-                space, SkMatrix::I(), space, kNone_ColorSpaceMatch, kTable_SrcGamma,
-                kTable_DstGamma));
-    } else {
-        return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
-                <kTable_SrcGamma, kTable_DstGamma, kNone_ColorSpaceMatch>
-                (space, SkMatrix::I(), space));
-    }
+    return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
+            <kTable_SrcGamma, kTable_DstGamma, kNone_ColorSpaceMatch>
+            (space, SkMatrix::I(), space));
 }
diff --git a/src/core/SkColorSpaceXformPriv.h b/src/core/SkColorSpaceXformPriv.h
index 21400eb..5a2bb97 100644
--- a/src/core/SkColorSpaceXformPriv.h
+++ b/src/core/SkColorSpaceXformPriv.h
@@ -81,4 +81,24 @@
     }
 }
 
+static inline SkColorSpaceXform::ColorFormat select_xform_format(SkColorType colorType) {
+    switch (colorType) {
+        case kRGBA_8888_SkColorType:
+            return SkColorSpaceXform::kRGBA_8888_ColorFormat;
+        case kBGRA_8888_SkColorType:
+            return SkColorSpaceXform::kBGRA_8888_ColorFormat;
+        case kRGBA_F16_SkColorType:
+            return SkColorSpaceXform::kRGBA_F16_ColorFormat;
+        case kIndex_8_SkColorType:
+#ifdef SK_PMCOLOR_IS_RGBA
+            return SkColorSpaceXform::kRGBA_8888_ColorFormat;
+#else
+            return SkColorSpaceXform::kBGRA_8888_ColorFormat;
+#endif
+        default:
+            SkASSERT(false);
+            return SkColorSpaceXform::kRGBA_8888_ColorFormat;
+    }
+}
+
 #endif
diff --git a/src/core/SkColorSpaceXform_Base.h b/src/core/SkColorSpaceXform_Base.h
index 3d17f67..e208573 100644
--- a/src/core/SkColorSpaceXform_Base.h
+++ b/src/core/SkColorSpaceXform_Base.h
@@ -56,6 +56,9 @@
                  int count, SkAlphaType alphaType) const override;
 
 private:
+    bool applyPipeline(ColorFormat dstFormat, void* dst, ColorFormat srcFormat, const void* src,
+                       int count, SkAlphaType alphaType) const;
+
     SkColorSpaceXform_XYZ(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst,
                           SkColorSpace_XYZ* dstSpace);
 
@@ -65,7 +68,8 @@
     const uint8_t*            fDstGammaTables[3];
     sk_sp<SkData>             fDstStorage;
 
-    float                     fSrcToDst[16];
+    // Holds a 3x4 matrix.  Padding is useful for vector loading.
+    float                     fSrcToDst[13];
 
     friend class SkColorSpaceXform;
     friend std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space);
@@ -86,32 +90,6 @@
     int            fCount;
 };
 
-class SkColorSpaceXform_Pipeline : public SkColorSpaceXform_Base {
-protected:
-    virtual bool onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat, const void* src,
-                         int count, SkAlphaType alphaType) const;
-
-private:
-    SkColorSpaceXform_Pipeline(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst,
-                               SkColorSpace_XYZ* dstSpace, ColorSpaceMatch csm, SrcGamma srcGamma,
-                               DstGamma dstGamma);
-
-    // Contain pointers into storage or pointers into precomputed tables.
-    const float*              fSrcGammaTables[3];
-    SkAutoTMalloc<float>      fSrcStorage;
-    const uint8_t*            fDstGammaTables[3];
-    sk_sp<SkData>             fDstStorage;
-
-    float                     fSrcToDst[12];
-
-    ColorSpaceMatch           fCSM;
-    SrcGamma                  fSrcGamma;
-    DstGamma                  fDstGamma;
-
-    friend class SkColorSpaceXform;
-    friend std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space);
-};
-
 // For testing.  Bypasses opts for when src and dst color spaces are equal.
 std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space);
 
diff --git a/src/core/SkColorSpace_ICC.cpp b/src/core/SkColorSpace_ICC.cpp
index 29291a3..ff4b901 100644
--- a/src/core/SkColorSpace_ICC.cpp
+++ b/src/core/SkColorSpace_ICC.cpp
@@ -5,11 +5,12 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkColorSpace.h"
+#include "SkColorSpacePriv.h"
 #include "SkColorSpace_A2B.h"
 #include "SkColorSpace_Base.h"
 #include "SkColorSpace_XYZ.h"
-#include "SkColorSpacePriv.h"
 #include "SkEndian.h"
 #include "SkFixed.h"
 #include "SkICCPriv.h"
diff --git a/src/core/SkColorSpace_XYZ.cpp b/src/core/SkColorSpace_XYZ.cpp
index 4329ed8..baf9969 100644
--- a/src/core/SkColorSpace_XYZ.cpp
+++ b/src/core/SkColorSpace_XYZ.cpp
@@ -5,17 +5,17 @@
  * found in the LICENSE file.
  */
 
-#include "SkChecksum.h"
 #include "SkColorSpace_XYZ.h"
 #include "SkColorSpacePriv.h"
 #include "SkColorSpaceXform_Base.h"
+#include "SkOpts.h"
 
 SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
     : INHERITED(nullptr)
     , fGammaNamed(gammaNamed)
     , fGammas(nullptr)
     , fToXYZD50(toXYZD50)
-    , fToXYZD50Hash(SkGoodHash()(toXYZD50))
+    , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
     , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
 {}
 
@@ -25,7 +25,7 @@
     , fGammaNamed(gammaNamed)
     , fGammas(std::move(gammas))
     , fToXYZD50(toXYZD50)
-    , fToXYZD50Hash(SkGoodHash()(toXYZD50))
+    , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
     , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) {
     SkASSERT(!fGammas || 3 == fGammas->channels());
     if (fGammas) {
diff --git a/src/core/SkConfig8888.cpp b/src/core/SkConfig8888.cpp
index 08635a2..b882146 100644
--- a/src/core/SkConfig8888.cpp
+++ b/src/core/SkConfig8888.cpp
@@ -7,10 +7,13 @@
 
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkColorSpaceXform.h"
+#include "SkColorSpaceXformPriv.h"
 #include "SkConfig8888.h"
 #include "SkColorPriv.h"
 #include "SkDither.h"
 #include "SkMathPriv.h"
+#include "SkPM4fPriv.h"
 #include "SkRasterPipeline.h"
 #include "SkUnPreMultiply.h"
 
@@ -35,11 +38,6 @@
         src_srgb = false;   // untagged dst means ignore tags on src
     }
 
-    // Nothing in the pipeline distinguishes between r,g,b, so if we need to swap, it doesn't
-    // matter when we do it. Just need to track if we need it at all.
-    const bool swap_rb = (kBGRA_8888_SkColorType == srcInfo.colorType())
-                       ^ (kBGRA_8888_SkColorType == dstInfo.colorType());
-
     SkRasterPipeline pipeline;
 
     switch (srcInfo.colorType()) {
@@ -49,7 +47,7 @@
             if (src_srgb) {
                 pipeline.append_from_srgb(srcInfo.alphaType());
             }
-            if (swap_rb) {
+            if (kBGRA_8888_SkColorType == srcInfo.colorType()) {
                 pipeline.append(SkRasterPipeline::swap_rb);
             }
             break;
@@ -65,6 +63,11 @@
             return false;   // src colortype unsupported
     }
 
+    float matrix[12];
+    if (!append_gamut_transform(&pipeline, matrix, srcInfo.colorSpace(), dstInfo.colorSpace())) {
+        return false;
+    }
+
     SkAlphaType sat = srcInfo.alphaType();
     SkAlphaType dat = dstInfo.alphaType();
     if (sat == kPremul_SkAlphaType && dat == kUnpremul_SkAlphaType) {
@@ -76,6 +79,9 @@
     switch (dstInfo.colorType()) {
         case kRGBA_8888_SkColorType:
         case kBGRA_8888_SkColorType:
+            if (kBGRA_8888_SkColorType == dstInfo.colorType()) {
+                pipeline.append(SkRasterPipeline::swap_rb);
+            }
             if (dst_srgb) {
                 pipeline.append(SkRasterPipeline::to_srgb);
             }
@@ -317,6 +323,73 @@
     return true;
 }
 
+static inline bool optimized_color_xform(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo) {
+    if (kUnpremul_SkAlphaType == dstInfo.alphaType() && kPremul_SkAlphaType == srcInfo.alphaType())
+    {
+        return false;
+    }
+
+    switch (dstInfo.colorType()) {
+        case kRGBA_8888_SkColorType:
+        case kBGRA_8888_SkColorType:
+        case kRGBA_F16_SkColorType:
+            break;
+        default:
+            return false;
+    }
+
+    switch (srcInfo.colorType()) {
+        case kRGBA_8888_SkColorType:
+        case kBGRA_8888_SkColorType:
+            break;
+        default:
+            return false;
+    }
+
+    return true;
+}
+
+static inline void apply_color_xform(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB,
+                                     const SkImageInfo& srcInfo, const void* srcPixels,
+                                     size_t srcRB) {
+    SkColorSpaceXform::ColorFormat dstFormat = select_xform_format(dstInfo.colorType());
+    SkColorSpaceXform::ColorFormat srcFormat = select_xform_format(srcInfo.colorType());
+    SkAlphaType xformAlpha;
+    switch (srcInfo.alphaType()) {
+        case kOpaque_SkAlphaType:
+            xformAlpha = kOpaque_SkAlphaType;
+            break;
+        case kPremul_SkAlphaType:
+            SkASSERT(kPremul_SkAlphaType == dstInfo.alphaType());
+
+            // This signal means: copy the src alpha to the dst, do not premultiply (in this
+            // case because the pixels are already premultiplied).
+            xformAlpha = kUnpremul_SkAlphaType;
+            break;
+        case kUnpremul_SkAlphaType:
+            SkASSERT(kPremul_SkAlphaType == dstInfo.alphaType() ||
+                     kUnpremul_SkAlphaType == dstInfo.alphaType());
+
+            xformAlpha = dstInfo.alphaType();
+            break;
+        default:
+            SkASSERT(false);
+            xformAlpha = kUnpremul_SkAlphaType;
+            break;
+    }
+
+    std::unique_ptr<SkColorSpaceXform> xform = SkColorSpaceXform::New(srcInfo.colorSpace(),
+                                                                      dstInfo.colorSpace());
+    SkASSERT(xform);
+
+    for (int y = 0; y < dstInfo.height(); y++) {
+        SkAssertResult(xform->apply(dstFormat, dstPixels, srcFormat, srcPixels, dstInfo.width(),
+                       xformAlpha));
+        dstPixels = SkTAddOffset<void>(dstPixels, dstRB);
+        srcPixels = SkTAddOffset<const void>(srcPixels, srcRB);
+    }
+}
+
 bool SkPixelInfo::CopyPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB,
                              const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRB,
                              SkColorTable* ctable) {
@@ -330,6 +403,23 @@
         return false;   // can't convert from alpha to non-alpha
     }
 
+    if (dstInfo.colorSpace() &&
+        SkColorSpace_Base::Type::kXYZ != as_CSB(dstInfo.colorSpace())->type())
+    {
+        return false;   // unsupported dst space
+    }
+
+    const bool srcIsF16 = kRGBA_F16_SkColorType == srcInfo.colorType();
+    const bool dstIsF16 = kRGBA_F16_SkColorType == dstInfo.colorType();
+    if (srcIsF16 || dstIsF16) {
+        if (!srcInfo.colorSpace() || !dstInfo.colorSpace() ||
+           (srcIsF16 && !srcInfo.colorSpace()->gammaIsLinear()) ||
+           (dstIsF16 && !dstInfo.colorSpace()->gammaIsLinear()))
+        {
+            return false;
+        }
+    }
+
     const int width = srcInfo.width();
     const int height = srcInfo.height();
 
@@ -344,8 +434,10 @@
         return true;
     }
 
+    const bool isColorAware = srcInfo.colorSpace() && dstInfo.colorSpace();
+
     // Handle fancy alpha swizzling if both are ARGB32
-    if (4 == srcInfo.bytesPerPixel() && 4 == dstInfo.bytesPerPixel()) {
+    if (4 == srcInfo.bytesPerPixel() && 4 == dstInfo.bytesPerPixel() && !isColorAware) {
         SkDstPixelInfo dstPI;
         dstPI.fColorType = dstInfo.colorType();
         dstPI.fAlphaType = dstInfo.alphaType();
@@ -361,13 +453,21 @@
         return srcPI.convertPixelsTo(&dstPI, width, height);
     }
 
+    if (isColorAware && optimized_color_xform(dstInfo, srcInfo)) {
+        apply_color_xform(dstInfo, dstPixels, dstRB, srcInfo, srcPixels, srcRB);
+        return true;
+    }
+
     // If they agree on colorType and the alphaTypes are compatible, then we just memcpy.
     // Note: we've already taken care of 32bit colortypes above.
     if (srcInfo.colorType() == dstInfo.colorType()) {
         switch (srcInfo.colorType()) {
+            case kRGBA_F16_SkColorType:
+                if (!SkColorSpace::Equals(srcInfo.colorSpace(), dstInfo.colorSpace())) {
+                    break;
+                }
             case kIndex_8_SkColorType:
             case kARGB_4444_SkColorType:
-            case kRGBA_F16_SkColorType:
                 if (srcInfo.alphaType() != dstInfo.alphaType()) {
                     break;
                 }
diff --git a/src/core/SkDistanceFieldGen.cpp b/src/core/SkDistanceFieldGen.cpp
index 7e4675b..f227d6a 100644
--- a/src/core/SkDistanceFieldGen.cpp
+++ b/src/core/SkDistanceFieldGen.cpp
@@ -5,8 +5,10 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkDistanceFieldGen.h"
 #include "SkPoint.h"
+#include "SkTemplates.h"
 
 struct DFData {
     float   fAlpha;      // alpha value of source texel
diff --git a/include/core/SkDraw.h b/src/core/SkDraw.h
similarity index 100%
rename from include/core/SkDraw.h
rename to src/core/SkDraw.h
diff --git a/src/core/SkEdge.cpp b/src/core/SkEdge.cpp
index 216604b..f8e8d98 100644
--- a/src/core/SkEdge.cpp
+++ b/src/core/SkEdge.cpp
@@ -157,16 +157,22 @@
     return dx;
 }
 
-static inline int diff_to_shift(SkFDot6 dx, SkFDot6 dy)
+static inline int diff_to_shift(SkFDot6 dx, SkFDot6 dy, int shiftAA = 2)
 {
     // cheap calc of distance from center of p0-p2 to the center of the curve
     SkFDot6 dist = cheap_distance(dx, dy);
 
     // shift down dist (it is currently in dot6)
-    // down by 5 should give us 1/2 pixel accuracy (assuming our dist is accurate...)
+    // down by 3 should give us 1/8 pixel accuracy (assuming our dist is accurate...)
     // this is chosen by heuristic: make it as big as possible (to minimize segments)
     // ... but small enough so that our curves still look smooth
+    // When shift > 0, we're using AA and everything is scaled up so we can
+    // lower the accuracy.
+#ifdef SK_SUPPORT_LEGACY_QUAD_SHIFT
     dist = (dist + (1 << 4)) >> 5;
+#else
+    dist = (dist + (1 << 4)) >> (3 + shiftAA);
+#endif
 
     // each subdivision (shift value) cuts this dist (error) by 1/4
     return (32 - SkCLZ(dist)) >> 1;
@@ -214,7 +220,10 @@
     {
         SkFDot6 dx = (SkLeftShift(x1, 1) - x0 - x2) >> 2;
         SkFDot6 dy = (SkLeftShift(y1, 1) - y0 - y2) >> 2;
-        shift = diff_to_shift(dx, dy);
+        // This is a little confusing:
+        // before this line, shift is the scale up factor for AA;
+        // after this line, shift is the fCurveShift.
+        shift = diff_to_shift(dx, dy, shift);
         SkASSERT(shift >= 0);
     }
     // need at least 1 subdivision for our bias trick
diff --git a/src/core/SkFindAndPlaceGlyph.h b/src/core/SkFindAndPlaceGlyph.h
index 4e0c9f1..c17dccb 100644
--- a/src/core/SkFindAndPlaceGlyph.h
+++ b/src/core/SkFindAndPlaceGlyph.h
@@ -436,10 +436,7 @@
 
         SkPoint findAndPositionGlyph(
             const char** text, SkPoint position, ProcessOneGlyph&& processOneGlyph) override {
-            if (!SkScalarsAreFinite(position.fX, position.fY)) {
-                return position;
-            }
-            SkPoint finalPosition = position;
+
             if (kTextAlignment != SkPaint::kLeft_Align) {
                 // Get the width of an un-sub-pixel positioned glyph for calculating the
                 // alignment. This is not needed for kLeftAlign because its adjustment is
@@ -450,26 +447,28 @@
                 if (metricGlyph.fWidth <= 0) {
                     // Exiting early, be sure to update text pointer.
                     *text = tempText;
-                    return finalPosition + SkPoint{SkFloatToScalar(metricGlyph.fAdvanceX),
-                                                   SkFloatToScalar(metricGlyph.fAdvanceY)};
+                    return position + SkPoint{SkFloatToScalar(metricGlyph.fAdvanceX),
+                                              SkFloatToScalar(metricGlyph.fAdvanceY)};
                 }
 
                 // Adjust the final position by the alignment adjustment.
-                finalPosition -= TextAlignmentAdjustment(kTextAlignment, metricGlyph);
+                position -= TextAlignmentAdjustment(kTextAlignment, metricGlyph);
             }
 
             // Find the glyph.
-            SkIPoint lookupPosition = SubpixelAlignment(kAxisAlignment, finalPosition);
+            SkIPoint lookupPosition = SkScalarsAreFinite(position.fX, position.fY)
+                                      ? SubpixelAlignment(kAxisAlignment, position)
+                                      : SkIPoint{0, 0};
             const SkGlyph& renderGlyph =
                 fGlyphFinder->lookupGlyphXY(text, lookupPosition.fX, lookupPosition.fY);
 
             // If the glyph has no width (no pixels) then don't bother processing it.
             if (renderGlyph.fWidth > 0) {
-                processOneGlyph(renderGlyph, finalPosition,
+                processOneGlyph(renderGlyph, position,
                                 SubpixelPositionRounding(kAxisAlignment));
             }
-            return finalPosition + SkPoint{SkFloatToScalar(renderGlyph.fAdvanceX),
-                                           SkFloatToScalar(renderGlyph.fAdvanceY)};
+            return position + SkPoint{SkFloatToScalar(renderGlyph.fAdvanceX),
+                                      SkFloatToScalar(renderGlyph.fAdvanceY)};
         }
 
     private:
diff --git a/src/core/SkFontMgr.cpp b/src/core/SkFontMgr.cpp
index 57f82b0..03ac2eb 100644
--- a/src/core/SkFontMgr.cpp
+++ b/src/core/SkFontMgr.cpp
@@ -167,15 +167,15 @@
     return this->onLegacyCreateTypeface(familyName, style);
 }
 
-SkFontMgr* SkFontMgr::RefDefault() {
+sk_sp<SkFontMgr> SkFontMgr::RefDefault() {
     static SkOnce once;
-    static SkFontMgr* singleton;
+    static sk_sp<SkFontMgr> singleton;
 
     once([]{
-        SkFontMgr* fm = SkFontMgr::Factory();
-        singleton = fm ? fm : new SkEmptyFontMgr;
+        sk_sp<SkFontMgr> fm = SkFontMgr::Factory();
+        singleton = fm ? std::move(fm) : sk_make_sp<SkEmptyFontMgr>();
     });
-    return SkRef(singleton);
+    return singleton;
 }
 
 /**
diff --git a/src/core/SkFontStream.cpp b/src/core/SkFontStream.cpp
index b2ffe8d..e9f23f9 100644
--- a/src/core/SkFontStream.cpp
+++ b/src/core/SkFontStream.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkEndian.h"
 #include "SkFontStream.h"
 #include "SkStream.h"
diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp
index cb515e1..175ea50 100644
--- a/src/core/SkGeometry.cpp
+++ b/src/core/SkGeometry.cpp
@@ -15,12 +15,6 @@
     return vector;
 }
 
-/** If defined, this makes eval_quad and eval_cubic do more setup (sometimes
-    involving integer multiplies by 2 or 3, but fewer calls to SkScalarMul.
-    May also introduce overflow of fixed when we compute our setup.
-*/
-//    #define DIRECT_EVAL_OF_POLYNOMIALS
-
 ////////////////////////////////////////////////////////////////////////
 
 static int is_not_monotonic(SkScalar a, SkScalar b, SkScalar c) {
@@ -289,33 +283,6 @@
 ///// CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS /////
 //////////////////////////////////////////////////////////////////////////////
 
-#ifdef SK_SUPPORT_LEGACY_EVAL_CUBIC
-static SkScalar eval_cubic(const SkScalar src[], SkScalar t) {
-    SkASSERT(src);
-    SkASSERT(t >= 0 && t <= SK_Scalar1);
-
-    if (t == 0) {
-        return src[0];
-    }
-
-#ifdef DIRECT_EVAL_OF_POLYNOMIALS
-    SkScalar D = src[0];
-    SkScalar A = src[6] + 3*(src[2] - src[4]) - D;
-    SkScalar B = 3*(src[4] - src[2] - src[2] + D);
-    SkScalar C = 3*(src[2] - D);
-
-    return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
-#else
-    SkScalar    ab = SkScalarInterp(src[0], src[2], t);
-    SkScalar    bc = SkScalarInterp(src[2], src[4], t);
-    SkScalar    cd = SkScalarInterp(src[4], src[6], t);
-    SkScalar    abc = SkScalarInterp(ab, bc, t);
-    SkScalar    bcd = SkScalarInterp(bc, cd, t);
-    return SkScalarInterp(abc, bcd, t);
-#endif
-}
-#endif
-
 static SkVector eval_cubic_derivative(const SkPoint src[4], SkScalar t) {
     SkQuadCoeff coeff;
     Sk2s P0 = from_point(src[0]);
@@ -346,11 +313,7 @@
     SkASSERT(t >= 0 && t <= SK_Scalar1);
 
     if (loc) {
-#ifdef SK_SUPPORT_LEGACY_EVAL_CUBIC
-        loc->set(eval_cubic(&src[0].fX, t), eval_cubic(&src[0].fY, t));
-#else
         *loc = to_point(SkCubicCoeff(src).eval(t));
-#endif
     }
     if (tangent) {
         // The derivative equation returns a zero tangent vector when t is 0 or 1, and the
diff --git a/src/core/SkGpuBlurUtils.cpp b/src/core/SkGpuBlurUtils.cpp
index e3463a4..b3b523a 100644
--- a/src/core/SkGpuBlurUtils.cpp
+++ b/src/core/SkGpuBlurUtils.cpp
@@ -84,7 +84,7 @@
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
     SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
                                                -SkIntToScalar(srcOffset.y()));
-    renderTargetContext->fillRectWithLocalMatrix(clip, paint, GrAA::kNo, SkMatrix::I(),
+    renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
                                                  SkRect::Make(dstRect), localMatrix);
 }
 
@@ -112,7 +112,7 @@
             true, sigmaX, sigmaY));
     paint.addColorFragmentProcessor(std::move(conv));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    renderTargetContext->fillRectWithLocalMatrix(clip, paint, GrAA::kNo, SkMatrix::I(),
+    renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
                                                  SkRect::Make(dstRect), localMatrix);
 }
 
@@ -268,10 +268,9 @@
         matrix.setIDiv(srcTexture->width(), srcTexture->height());
         SkIRect dstRect(srcRect);
         if (srcBounds && i == 1) {
-            SkRect domain;
-            matrix.mapRect(&domain, SkRect::Make(*srcBounds));
-            domain.inset((i < scaleFactorX) ? SK_ScalarHalf / srcTexture->width() : 0.0f,
-                         (i < scaleFactorY) ? SK_ScalarHalf / srcTexture->height() : 0.0f);
+            SkRect domain = SkRect::Make(*srcBounds);
+            domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f,
+                         (i < scaleFactorY) ? SK_ScalarHalf : 0.0f);
             sk_sp<GrFragmentProcessor> fp(GrTextureDomainEffect::Make(
                                                         srcTexture.get(),
                                                         nullptr,
@@ -289,7 +288,7 @@
         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
         shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);
 
-        dstRenderTargetContext->fillRectToRect(clip, paint, GrAA::kNo, SkMatrix::I(),
+        dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
                                                SkRect::Make(dstRect), SkRect::Make(srcRect));
 
         srcRenderTargetContext = dstRenderTargetContext;
@@ -381,7 +380,7 @@
         SkIRect dstRect(srcRect);
         scale_irect(&dstRect, scaleFactorX, scaleFactorY);
 
-        dstRenderTargetContext->fillRectToRect(clip, paint, GrAA::kNo, SkMatrix::I(),
+        dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
                                                SkRect::Make(dstRect), SkRect::Make(srcRect));
 
         srcRenderTargetContext = dstRenderTargetContext;
diff --git a/src/core/SkICC.cpp b/src/core/SkICC.cpp
index b47cc71..6b09f29 100644
--- a/src/core/SkICC.cpp
+++ b/src/core/SkICC.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkColorSpace_Base.h"
 #include "SkColorSpace_XYZ.h"
 #include "SkColorSpacePriv.h"
diff --git a/src/core/SkImageCacherator.cpp b/src/core/SkImageCacherator.cpp
index 21b112f..ed36d66 100644
--- a/src/core/SkImageCacherator.cpp
+++ b/src/core/SkImageCacherator.cpp
@@ -246,9 +246,10 @@
     }
 
     const uint32_t pixelOpsFlags = 0;
-    if (!tex->readPixels(0, 0, bitmap->width(), bitmap->height(),
+    if (!tex->readPixels(fInfo.colorSpace(), 0, 0, bitmap->width(), bitmap->height(),
                          SkImageInfo2GrPixelConfig(cacheInfo, *tex->getContext()->caps()),
-                         bitmap->getPixels(), bitmap->rowBytes(), pixelOpsFlags)) {
+                         cacheInfo.colorSpace(), bitmap->getPixels(), bitmap->rowBytes(),
+                         pixelOpsFlags)) {
         bitmap->reset();
         return false;
     }
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 2ec441f..8ce5e8e 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -8,6 +8,7 @@
 #include "SkImageFilter.h"
 
 #include "SkCanvas.h"
+#include "SkColorSpace_Base.h"
 #include "SkFuzzLogging.h"
 #include "SkImageFilterCache.h"
 #include "SkLocalMatrixImageFilter.h"
@@ -297,7 +298,8 @@
     SkRect srcRect = SkRect::Make(bounds);
     SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
     GrFixedClip clip(dstIRect);
-    renderTargetContext->fillRectToRect(clip, paint, GrAA::kNo, SkMatrix::I(), dstRect, srcRect);
+    renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
+                                        srcRect);
 
     return SkSpecialImage::MakeDeferredFromGpu(context, dstIRect,
                                                kNeedNewImageUniqueID_SpecialImage,
@@ -344,6 +346,37 @@
     return dstBounds->intersect(ctx.clipBounds());
 }
 
+#if SK_SUPPORT_GPU
+sk_sp<SkSpecialImage> SkImageFilter::ImageToColorSpace(SkSpecialImage* src,
+                                                       const OutputProperties& outProps) {
+    // There are several conditions that determine if we actually need to convert the source to the
+    // destination's color space. Rather than duplicate that logic here, just try to make an xform
+    // object. If that produces something, then both are tagged, and the source is in a different
+    // gamut than the dest. There is some overhead to making the xform, but those are cached, and
+    // if we get one back, that means we're about to use it during the conversion anyway.
+    sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(src->getColorSpace(),
+                                                                       outProps.colorSpace());
+
+    if (!colorSpaceXform) {
+        // No xform needed, just return the original image
+        return sk_ref_sp(src);
+    }
+
+    sk_sp<SkSpecialSurface> surf(src->makeSurface(outProps,
+                                                  SkISize::Make(src->width(), src->height())));
+    if (!surf) {
+        return sk_ref_sp(src);
+    }
+
+    SkCanvas* canvas = surf->getCanvas();
+    SkASSERT(canvas);
+    SkPaint p;
+    p.setBlendMode(SkBlendMode::kSrc);
+    src->draw(canvas, 0, 0, &p);
+    return surf->makeImageSnapshot();
+}
+#endif
+
 // Return a larger (newWidth x newHeight) copy of 'src' with black padding
 // around it.
 static sk_sp<SkSpecialImage> pad_image(SkSpecialImage* src,
diff --git a/src/core/SkImagePriv.h b/src/core/SkImagePriv.h
index 5b470e2..1d19714 100644
--- a/src/core/SkImagePriv.h
+++ b/src/core/SkImagePriv.h
@@ -33,11 +33,6 @@
                                    const SkMatrix* localMatrix, SkCopyPixelsMode,
                                    SkTBlitterAllocator* alloc);
 
-// Call this if you explicitly want to use/share this pixelRef in the image
-extern sk_sp<SkImage> SkMakeImageFromPixelRef(const SkImageInfo&, SkPixelRef*,
-                                              const SkIPoint& pixelRefOrigin,
-                                              size_t rowBytes);
-
 /**
  *  Examines the bitmap to decide if it can share the existing pixelRef, or
  *  if it needs to make a deep-copy of the pixels.
diff --git a/src/core/SkLRUCache.h b/src/core/SkLRUCache.h
index f7941bb..7f3805c 100644
--- a/src/core/SkLRUCache.h
+++ b/src/core/SkLRUCache.h
@@ -17,6 +17,18 @@
  */
 template <typename K, typename V, typename HashK = SkGoodHash>
 class SkLRUCache : public SkNoncopyable {
+private:
+    struct Entry {
+        Entry(const K& key, V&& value)
+        : fKey(key)
+        , fValue(std::move(value)) {}
+
+        K fKey;
+        V fValue;
+
+        SK_DECLARE_INTERNAL_LLIST_INTERFACE(Entry);
+    };
+
 public:
     explicit SkLRUCache(int maxCount)
     : fMaxCount(maxCount) {}
@@ -57,18 +69,24 @@
         return fMap.count();
     }
 
+    template <typename Fn>  // f(V*)
+    void foreach(Fn&& fn) {
+        typename SkTInternalLList<Entry>::Iter iter;
+        for (Entry* e = iter.init(fLRU, SkTInternalLList<Entry>::Iter::kHead_IterStart); e;
+             e = iter.next()) {
+            fn(&e->fValue);
+        }
+    }
+
+    void reset() {
+        fMap.reset();
+        for (Entry* e = fLRU.head(); e; e = fLRU.head()) {
+            fLRU.remove(e);
+            delete e;
+        }
+    }
+
 private:
-    struct Entry {
-        Entry(const K& key, V&& value)
-        : fKey(key)
-        , fValue(std::move(value)) {}
-
-        K fKey;
-        V fValue;
-
-        SK_DECLARE_INTERNAL_LLIST_INTERFACE(Entry);
-    };
-
     struct Traits {
         static const K& GetKey(Entry* e) {
             return e->fKey;
diff --git a/src/core/SkLinearBitmapPipeline_sample.h b/src/core/SkLinearBitmapPipeline_sample.h
index 8e53136..0bfe8f2 100644
--- a/src/core/SkLinearBitmapPipeline_sample.h
+++ b/src/core/SkLinearBitmapPipeline_sample.h
@@ -10,6 +10,7 @@
 
 #include <tuple>
 
+#include "SkAutoMalloc.h"
 #include "SkColor.h"
 #include "SkColorPriv.h"
 #include "SkFixed.h"
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp
index 85ee38d..59dbe2e 100644
--- a/src/core/SkMaskFilter.cpp
+++ b/src/core/SkMaskFilter.cpp
@@ -5,15 +5,15 @@
  * found in the LICENSE file.
  */
 
-
 #include "SkMaskFilter.h"
+
+#include "SkAutoMalloc.h"
 #include "SkBlitter.h"
-#include "SkDraw.h"
 #include "SkCachedData.h"
+#include "SkDraw.h"
 #include "SkPath.h"
-#include "SkRasterClip.h"
 #include "SkRRect.h"
-#include "SkTypes.h"
+#include "SkRasterClip.h"
 
 #if SK_SUPPORT_GPU
 #include "GrTexture.h"
@@ -314,20 +314,19 @@
     return false;
 }
 
- bool SkMaskFilter::directFilterMaskGPU(GrTextureProvider* texProvider,
-                                        GrRenderTargetContext* renderTargetContext,
-                                        GrPaint* grp,
-                                        const GrClip&,
-                                        const SkMatrix& viewMatrix,
-                                        const SkStrokeRec& strokeRec,
-                                        const SkPath& path) const {
+bool SkMaskFilter::directFilterMaskGPU(GrTextureProvider* texProvider,
+                                       GrRenderTargetContext* renderTargetContext,
+                                       GrPaint&&,
+                                       const GrClip&,
+                                       const SkMatrix& viewMatrix,
+                                       const SkStrokeRec& strokeRec,
+                                       const SkPath& path) const {
     return false;
 }
 
-
 bool SkMaskFilter::directFilterRRectMaskGPU(GrContext*,
                                             GrRenderTargetContext* renderTargetContext,
-                                            GrPaint* grp,
+                                            GrPaint&&,
                                             const GrClip&,
                                             const SkMatrix& viewMatrix,
                                             const SkStrokeRec& strokeRec,
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index a5df8dd..fb89a67 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -440,7 +440,11 @@
     switch (this->getTextEncoding()) {
         case SkPaint::kUTF8_TextEncoding:
             while (text < stop) {
-                *gptr++ = cache->unicharToGlyph(SkUTF8_NextUnichar(&text));
+                SkUnichar u = SkUTF8_NextUnicharWithError(&text, stop);
+                if (u < 0) {
+                    return 0;  // bad UTF-8 sequence
+                }
+                *gptr++ = cache->unicharToGlyph(u);
             }
             break;
         case SkPaint::kUTF16_TextEncoding: {
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 7f233f2..53678de 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -2054,7 +2054,7 @@
 }
 
 size_t SkPath::readFromMemory(const void* storage, size_t length) {
-    SkRBufferWithSizeCheck buffer(storage, length);
+    SkRBuffer buffer(storage, length);
 
     int32_t packed;
     if (!buffer.readS32(&packed)) {
diff --git a/src/core/SkPathRef.cpp b/src/core/SkPathRef.cpp
index ef051f7..55ee991 100644
--- a/src/core/SkPathRef.cpp
+++ b/src/core/SkPathRef.cpp
@@ -186,6 +186,38 @@
     SkDEBUGCODE((*dst)->validate();)
 }
 
+// Given the verb array, deduce the required number of pts and conics,
+// or if an invalid verb is encountered, return false.
+static bool deduce_pts_conics(const uint8_t verbs[], int vCount, int* ptCountPtr,
+                              int* conicCountPtr) {
+    int ptCount = 0;
+    int conicCount = 0;
+    for (int i = 0; i < vCount; ++i) {
+        switch (verbs[i]) {
+            case SkPath::kMove_Verb:
+            case SkPath::kLine_Verb:
+                ptCount += 1;
+                break;
+            case SkPath::kConic_Verb:
+                conicCount += 1;
+                // fall-through
+            case SkPath::kQuad_Verb:
+                ptCount += 2;
+                break;
+            case SkPath::kCubic_Verb:
+                ptCount += 3;
+                break;
+            case SkPath::kClose_Verb:
+                break;
+            default:
+                return false;
+        }
+    }
+    *ptCountPtr = ptCount;
+    *conicCountPtr = conicCount;
+    return true;
+}
+
 SkPathRef* SkPathRef::CreateFromBuffer(SkRBuffer* buffer) {
     SkPathRef* ref = new SkPathRef;
 
@@ -231,6 +263,17 @@
         delete ref;
         return nullptr;
     }
+
+    // Check that the verbs are valid, and imply the correct number of pts and conics
+    {
+        int pCount, cCount;
+        if (!deduce_pts_conics(ref->verbsMemBegin(), ref->countVerbs(), &pCount, &cCount) ||
+            pCount != ref->countPoints() || cCount != ref->fConicWeights.count()) {
+            delete ref;
+            return nullptr;
+        }
+    }
+    
     ref->fBoundsIsDirty = false;
 
     // resetToSize clears fSegmentMask and fIsOval
diff --git a/src/core/SkPictureData.cpp b/src/core/SkPictureData.cpp
index 1aff1e4..9780b7d 100644
--- a/src/core/SkPictureData.cpp
+++ b/src/core/SkPictureData.cpp
@@ -4,7 +4,10 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+
 #include <new>
+
+#include "SkAutoMalloc.h"
 #include "SkImageGenerator.h"
 #include "SkPictureData.h"
 #include "SkPictureRecord.h"
diff --git a/src/core/SkPictureImageGenerator.cpp b/src/core/SkPictureImageGenerator.cpp
index 4de49f0..762654a 100644
--- a/src/core/SkPictureImageGenerator.cpp
+++ b/src/core/SkPictureImageGenerator.cpp
@@ -17,7 +17,7 @@
 class SkPictureImageGenerator : SkImageGenerator {
 public:
     static SkImageGenerator* Create(const SkISize&, const SkPicture*, const SkMatrix*,
-                                    const SkPaint*, sk_sp<SkColorSpace>);
+                                    const SkPaint*, SkImage::BitDepth, sk_sp<SkColorSpace>);
 
 protected:
     bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, SkPMColor ctable[],
@@ -42,20 +42,25 @@
 
 SkImageGenerator* SkPictureImageGenerator::Create(const SkISize& size, const SkPicture* picture,
                                                   const SkMatrix* matrix, const SkPaint* paint,
+                                                  SkImage::BitDepth bitDepth,
                                                   sk_sp<SkColorSpace> colorSpace) {
     if (!picture || size.isEmpty()) {
         return nullptr;
     }
 
-    SkColorType colorType;
-    if (!colorSpace || colorSpace->gammaCloseToSRGB()) {
-        colorType = kN32_SkColorType;
-    } else if (colorSpace->gammaIsLinear()) {
-        colorType = kRGBA_F16_SkColorType;
-    } else {
+    if (SkImage::BitDepth::kF16 == bitDepth && (!colorSpace || !colorSpace->gammaIsLinear())) {
         return nullptr;
     }
 
+    if (colorSpace && (!colorSpace->gammaCloseToSRGB() && !colorSpace->gammaIsLinear())) {
+        return nullptr;
+    }
+
+    SkColorType colorType = kN32_SkColorType;
+    if (SkImage::BitDepth::kF16 == bitDepth) {
+        colorType = kRGBA_F16_SkColorType;
+    }
+
     SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), colorType,
                                          kPremul_SkAlphaType, std::move(colorSpace));
     return new SkPictureImageGenerator(info, picture, matrix, paint);
@@ -134,8 +139,10 @@
 
 SkImageGenerator* SkImageGenerator::NewFromPicture(const SkISize& size, const SkPicture* picture,
                                                    const SkMatrix* matrix, const SkPaint* paint,
+                                                   SkImage::BitDepth bitDepth,
                                                    sk_sp<SkColorSpace> colorSpace) {
-    return SkPictureImageGenerator::Create(size, picture, matrix, paint, std::move(colorSpace));
+    return SkPictureImageGenerator::Create(size, picture, matrix, paint, bitDepth,
+                                           std::move(colorSpace));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkPictureShader.cpp b/src/core/SkPictureShader.cpp
index e34ca99..1f20623 100644
--- a/src/core/SkPictureShader.cpp
+++ b/src/core/SkPictureShader.cpp
@@ -227,7 +227,7 @@
 
         sk_sp<SkImage> tileImage(
             SkImage::MakeFromPicture(fPicture, tileSize, &tileMatrix, nullptr,
-                                     sk_ref_sp(dstColorSpace)));
+                                     SkImage::BitDepth::kU8, sk_ref_sp(dstColorSpace)));
         if (!tileImage) {
             return nullptr;
         }
diff --git a/src/core/SkRasterPipeline.cpp b/src/core/SkRasterPipeline.cpp
index 00ba81e..7c3536c 100644
--- a/src/core/SkRasterPipeline.cpp
+++ b/src/core/SkRasterPipeline.cpp
@@ -11,14 +11,7 @@
 SkRasterPipeline::SkRasterPipeline() {}
 
 void SkRasterPipeline::append(StockStage stage, void* ctx) {
-#ifdef SK_DEBUG
-    switch (stage) {
-        case from_srgb:
-        case from_srgb_d:
-            SkDEBUGFAIL("Please use append_srgb[_d]() instead.");
-        default: break;
-    }
-#endif
+    SkASSERT(stage != from_srgb);
     fStages.push_back({stage, ctx});
 }
 
@@ -34,6 +27,11 @@
 }
 
 std::function<void(size_t, size_t, size_t)> SkRasterPipeline::compile() const {
+#ifdef SK_RASTER_PIPELINE_HAS_JIT
+    if (auto fn = this->jit()) {
+        return fn;
+    }
+#endif
     return SkOpts::compile_pipeline(fStages.data(), SkToInt(fStages.size()));
 }
 
@@ -67,12 +65,3 @@
         this->append(SkRasterPipeline::clamp_a);
     }
 }
-
-void SkRasterPipeline::append_from_srgb_d(SkAlphaType at) {
-    //this->append(from_srgb_d);
-    fStages.push_back({from_srgb_d, nullptr});
-
-    if (at == kPremul_SkAlphaType) {
-        this->append(SkRasterPipeline::clamp_a_d);
-    }
-}
diff --git a/src/core/SkRasterPipeline.h b/src/core/SkRasterPipeline.h
index 3b18b73..d235b43 100644
--- a/src/core/SkRasterPipeline.h
+++ b/src/core/SkRasterPipeline.h
@@ -58,16 +58,16 @@
 
 #define SK_RASTER_PIPELINE_STAGES(M)                             \
     M(trace) M(registers)                                        \
-    M(move_src_dst) M(move_dst_src) M(swap_rb) M(swap_rb_d)      \
-    M(clamp_0) M(clamp_1) M(clamp_a) M(clamp_a_d)                \
+    M(move_src_dst) M(move_dst_src) M(swap)                      \
+    M(clamp_0) M(clamp_1) M(clamp_a)                             \
     M(unpremul) M(premul)                                        \
-    M(set_rgb)                                                   \
-    M(from_srgb) M(from_srgb_d) M(to_srgb)                       \
+    M(set_rgb) M(swap_rb)                                        \
+    M(from_srgb) M(to_srgb)                                      \
     M(from_2dot2) M(to_2dot2)                                    \
     M(constant_color) M(store_f32)                               \
-    M(load_565)  M(load_565_d)  M(store_565)                     \
-    M(load_f16)  M(load_f16_d)  M(store_f16)                     \
-    M(load_8888) M(load_8888_d) M(store_8888)                    \
+    M(load_565)  M(store_565)                                    \
+    M(load_f16)  M(store_f16)                                    \
+    M(load_8888) M(store_8888)                                   \
     M(load_tables) M(store_tables)                               \
     M(scale_u8) M(scale_1_float)                                 \
     M(lerp_u8) M(lerp_565) M(lerp_1_float)                       \
@@ -122,10 +122,11 @@
 
     // Conversion from sRGB can be subtly tricky when premultiplication is involved.
     // Use these helpers to keep things sane.
-    void append_from_srgb  (SkAlphaType);
-    void append_from_srgb_d(SkAlphaType);
+    void append_from_srgb(SkAlphaType);
 
 private:
+    std::function<void(size_t, size_t, size_t)> jit() const;
+
     std::vector<Stage> fStages;
 };
 
diff --git a/src/core/SkRasterPipelineBlitter.cpp b/src/core/SkRasterPipelineBlitter.cpp
index cbdb09f..9fad0b4 100644
--- a/src/core/SkRasterPipelineBlitter.cpp
+++ b/src/core/SkRasterPipelineBlitter.cpp
@@ -186,19 +186,21 @@
 void SkRasterPipelineBlitter::append_load_d(SkRasterPipeline* p) const {
     SkASSERT(supported(fDst.info()));
 
+    p->append(SkRasterPipeline::move_src_dst);
     switch (fDst.info().colorType()) {
-        case kRGB_565_SkColorType:   p->append(SkRasterPipeline::load_565_d,  &fDstPtr); break;
+        case kRGB_565_SkColorType:   p->append(SkRasterPipeline::load_565,  &fDstPtr); break;
         case kBGRA_8888_SkColorType:
-        case kRGBA_8888_SkColorType: p->append(SkRasterPipeline::load_8888_d, &fDstPtr); break;
-        case kRGBA_F16_SkColorType:  p->append(SkRasterPipeline::load_f16_d,  &fDstPtr); break;
+        case kRGBA_8888_SkColorType: p->append(SkRasterPipeline::load_8888, &fDstPtr); break;
+        case kRGBA_F16_SkColorType:  p->append(SkRasterPipeline::load_f16,  &fDstPtr); break;
         default: break;
     }
     if (fDst.info().colorType() == kBGRA_8888_SkColorType) {
-        p->append(SkRasterPipeline::swap_rb_d);
+        p->append(SkRasterPipeline::swap_rb);
     }
     if (fDst.info().gammaCloseToSRGB()) {
-        p->append_from_srgb_d(fDst.info().alphaType());
+        p->append_from_srgb(fDst.info().alphaType());
     }
+    p->append(SkRasterPipeline::swap);
 }
 
 void SkRasterPipelineBlitter::append_store(SkRasterPipeline* p) const {
diff --git a/src/core/SkRegion.cpp b/src/core/SkRegion.cpp
index b3b1831..1123cf0 100644
--- a/src/core/SkRegion.cpp
+++ b/src/core/SkRegion.cpp
@@ -1128,9 +1128,9 @@
 }
 
 size_t SkRegion::readFromMemory(const void* storage, size_t length) {
-    SkRBufferWithSizeCheck  buffer(storage, length);
-    SkRegion                tmp;
-    int32_t                 count;
+    SkRBuffer   buffer(storage, length);
+    SkRegion    tmp;
+    int32_t     count;
 
     if (buffer.readS32(&count) && (count >= 0) && buffer.read(&tmp.fBounds, sizeof(tmp.fBounds))) {
         if (count == 0) {
diff --git a/src/core/SkSRGB.h b/src/core/SkSRGB.h
index c182f32..88f2cd7 100644
--- a/src/core/SkSRGB.h
+++ b/src/core/SkSRGB.h
@@ -50,7 +50,7 @@
     return (x < 0.0048f).thenElse(lo, hi);
 }
 
-// [0.0f, 1.0f] -> [0.0f, 1.xf], for small x.  Correct after rounding.
+// [0.0f, 1.0f] -> [0.0f, 1.0f].  Correct after rounding.
 template <typename V>
 static inline V sk_linear_to_srgb_needs_round(const V& x) {
     // Tuned to round trip each sRGB byte after rounding.
@@ -60,9 +60,9 @@
 
     auto lo = 12.46f * x;
 
-    auto hi = SkNx_fma(V{+0.411192f}, ftrt,
-              SkNx_fma(V{+0.689206f}, sqrt,
-                       V{-0.0988f}));
+    auto hi = V::Min(1.0f, SkNx_fma(V{+0.411192f}, ftrt,
+                           SkNx_fma(V{+0.689206f}, sqrt,
+                                    V{-0.0988f})));
     return (x < 0.0043f).thenElse(lo, hi);
 }
 
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index c06222d..4835b52 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -5,8 +5,9 @@
  * found in the LICENSE file.
  */
 
-
 #include "SkScalerContext.h"
+
+#include "SkAutoMalloc.h"
 #include "SkAutoPixmapStorage.h"
 #include "SkColorPriv.h"
 #include "SkDescriptor.h"
@@ -16,13 +17,13 @@
 #include "SkMaskFilter.h"
 #include "SkMaskGamma.h"
 #include "SkMatrix22.h"
-#include "SkReadBuffer.h"
-#include "SkWriteBuffer.h"
 #include "SkPathEffect.h"
-#include "SkRasterizer.h"
 #include "SkRasterClip.h"
+#include "SkRasterizer.h"
+#include "SkReadBuffer.h"
 #include "SkStroke.h"
 #include "SkStrokeRec.h"
+#include "SkWriteBuffer.h"
 
 #define ComputeBWRowBytes(width)        (((unsigned)(width) + 7) >> 3)
 
diff --git a/src/core/SkScan_AAAPath.cpp b/src/core/SkScan_AAAPath.cpp
index f96e7b9..8c0a748 100644
--- a/src/core/SkScan_AAAPath.cpp
+++ b/src/core/SkScan_AAAPath.cpp
@@ -5,10 +5,11 @@
  * found in the LICENSE file.
  */
 
+#include "SkAnalyticEdge.h"
 #include "SkAntiRun.h"
+#include "SkAutoMalloc.h"
 #include "SkBlitter.h"
 #include "SkEdge.h"
-#include "SkAnalyticEdge.h"
 #include "SkEdgeBuilder.h"
 #include "SkGeometry.h"
 #include "SkPath.h"
@@ -17,8 +18,8 @@
 #include "SkRegion.h"
 #include "SkScan.h"
 #include "SkScanPriv.h"
-#include "SkTemplates.h"
 #include "SkTSort.h"
+#include "SkTemplates.h"
 #include "SkUtils.h"
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkStream.cpp b/src/core/SkStream.cpp
index e5fcc87..db9caee 100644
--- a/src/core/SkStream.cpp
+++ b/src/core/SkStream.cpp
@@ -70,11 +70,6 @@
 {
 }
 
-void SkWStream::newline()
-{
-    this->write("\n", 1);
-}
-
 void SkWStream::flush()
 {
 }
@@ -436,23 +431,6 @@
 
 ////////////////////////////////////////////////////////////////////////
 
-SkMemoryWStream::SkMemoryWStream(void* buffer, size_t size)
-    : fBuffer((char*)buffer), fMaxLength(size), fBytesWritten(0)
-{
-}
-
-bool SkMemoryWStream::write(const void* buffer, size_t size) {
-    size = SkTMin(size, fMaxLength - fBytesWritten);
-    if (size > 0) {
-        memcpy(fBuffer + fBytesWritten, buffer, size);
-        fBytesWritten += size;
-        return true;
-    }
-    return false;
-}
-
-////////////////////////////////////////////////////////////////////////
-
 static inline void sk_memcpy_4bytes(void* dst, const void* src, size_t size) {
     if (size == 4) {
         memcpy(dst, src, 4);
@@ -784,31 +762,7 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-
-void SkDebugWStream::newline()
-{
-#if defined(SK_DEBUG)
-    SkDebugf("\n");
-    fBytesWritten++;
-#endif
-}
-
-bool SkDebugWStream::write(const void* buffer, size_t size)
-{
-#if defined(SK_DEBUG)
-    char* s = new char[size+1];
-    memcpy(s, buffer, size);
-    s[size] = 0;
-    SkDebugf("%s", s);
-    delete[] s;
-    fBytesWritten += size;
-#endif
-    return true;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////
-
 
 static sk_sp<SkData> mmap_filename(const char path[]) {
     FILE* file = sk_fopen(path, kRead_SkFILE_Flag);
diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
index 8ff7910..e384df1 100644
--- a/src/core/SkStroke.cpp
+++ b/src/core/SkStroke.cpp
@@ -606,15 +606,23 @@
     if (count == 0) {
         return kLine_ReductionType;
     }
+    int rCount = 0;
+    // Now loop over the t-values, and reject any that evaluate to either end-point
     for (int index = 0; index < count; ++index) {
         SkScalar t = tValues[index];
-        SkEvalCubicAt(cubic, t, &reduction[index], nullptr, nullptr);
+        SkEvalCubicAt(cubic, t, &reduction[rCount], nullptr, nullptr);
+        if (reduction[rCount] != cubic[0] && reduction[rCount] != cubic[3]) {
+            ++rCount;
+        }
+    }
+    if (rCount == 0) {
+        return kLine_ReductionType;
     }
     static_assert(kQuad_ReductionType + 1 == kDegenerate_ReductionType, "enum_out_of_whack");
     static_assert(kQuad_ReductionType + 2 == kDegenerate2_ReductionType, "enum_out_of_whack");
     static_assert(kQuad_ReductionType + 3 == kDegenerate3_ReductionType, "enum_out_of_whack");
 
-    return (ReductionType) (kQuad_ReductionType + count);
+    return (ReductionType) (kQuad_ReductionType + rCount);
 }
 
 SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& conic,
diff --git a/src/core/SkUtils.cpp b/src/core/SkUtils.cpp
index 635d1b1..85ebb3e 100644
--- a/src/core/SkUtils.cpp
+++ b/src/core/SkUtils.cpp
@@ -19,10 +19,19 @@
     0xE5 << 24
 */
 
+static bool utf8_byte_is_valid(uint8_t c) {
+    return c < 0xF5 && (c & 0xFE) != 0xC0;
+}
+static bool utf8_byte_is_continuation(uint8_t c) {
+    return  (c & 0xC0) == 0x80;
+}
+static bool utf8_byte_is_leading_byte(uint8_t c) {
+    return utf8_byte_is_valid(c) && !utf8_byte_is_continuation(c);
+}
+
 #ifdef SK_DEBUG
     static void assert_utf8_leadingbyte(unsigned c) {
-        SkASSERT(c <= 0xF7);    // otherwise leading byte is too big (more than 4 bytes)
-        SkASSERT((c & 0xC0) != 0x80);   // can't begin with a middle char
+        SkASSERT(utf8_byte_is_leading_byte(SkToU8(c)));
     }
 
     int SkUTF8_LeadByteToCount(unsigned c) {
@@ -33,6 +42,29 @@
     #define assert_utf8_leadingbyte(c)
 #endif
 
+/**
+ * @returns -1  iff invalid UTF8 byte,
+ *           0  iff UTF8 continuation byte,
+ *           1  iff ASCII byte,
+ *           2  iff leading byte of 2-byte sequence,
+ *           3  iff leading byte of 3-byte sequence, and
+ *           4  iff leading byte of 4-byte sequence.
+ *
+ * I.e.: if return value > 0, then gives length of sequence.
+*/
+static int utf8_byte_type(uint8_t c) {
+    if (c < 0x80) {
+        return 1;
+    } else if (c < 0xC0) {
+        return 0;
+    } else if (c < 0xF5 && (c & 0xFE) != 0xC0) { // "octet values C0, C1, F5 to FF never appear"
+        return (((0xE5 << 24) >> ((unsigned)c >> 4 << 1)) & 3) + 1;
+    } else {
+        return -1;
+    }
+}
+static bool utf8_type_is_valid_leading_byte(int type) { return type > 0; }
+
 int SkUTF8_CountUnichars(const char utf8[]) {
     SkASSERT(utf8);
 
@@ -49,15 +81,28 @@
     return count;
 }
 
-int SkUTF8_CountUnichars(const char utf8[], size_t byteLength) {
+// SAFE: returns -1 if invalid UTF-8
+int SkUTF8_CountUnicharsWithError(const char utf8[], size_t byteLength) {
     SkASSERT(utf8 || 0 == byteLength);
 
     int         count = 0;
     const char* stop = utf8 + byteLength;
 
     while (utf8 < stop) {
-        utf8 += SkUTF8_LeadByteToCount(*(const uint8_t*)utf8);
-        count += 1;
+        int type = utf8_byte_type(*(const uint8_t*)utf8);
+        SkASSERT(type >= -1 && type <= 4);
+        if (!utf8_type_is_valid_leading_byte(type) ||
+            utf8 + type > stop) {  // Sequence extends beyond end.
+            return -1;
+        }
+        while(type-- > 1) {
+            ++utf8;
+            if (!utf8_byte_is_continuation(*(const uint8_t*)utf8)) {
+                return -1;
+            }
+        }
+        ++utf8;
+        ++count;
     }
     return count;
 }
@@ -83,6 +128,39 @@
     return c;
 }
 
+// SAFE: returns -1 on invalid UTF-8 sequence.
+SkUnichar SkUTF8_NextUnicharWithError(const char** ptr, const char* end) {
+    SkASSERT(ptr && *ptr);
+    SkASSERT(*ptr < end);
+    const uint8_t*  p = (const uint8_t*)*ptr;
+    int             c = *p;
+    int             hic = c << 24;
+
+    if (!utf8_byte_is_leading_byte(c)) {
+        return -1;
+    }
+    if (hic < 0) {
+        uint32_t mask = (uint32_t)~0x3F;
+        hic = SkLeftShift(hic, 1);
+        do {
+            ++p;
+            if (p >= (const uint8_t*)end) {
+                return -1;
+            }
+            // check before reading off end of array.
+            uint8_t nextByte = *p;
+            if (!utf8_byte_is_continuation(nextByte)) {
+                return -1;
+            }
+            c = (c << 6) | (nextByte & 0x3F);
+            mask <<= 5;
+        } while ((hic = SkLeftShift(hic, 1)) < 0);
+        c &= ~mask;
+    }
+    *ptr = (char*)p + 1;
+    return c;
+}
+
 SkUnichar SkUTF8_NextUnichar(const char** ptr) {
     SkASSERT(ptr && *ptr);
 
diff --git a/src/core/SkUtils.h b/src/core/SkUtils.h
index 26f19e6..e24dd52 100644
--- a/src/core/SkUtils.h
+++ b/src/core/SkUtils.h
@@ -9,6 +9,7 @@
 #define SkUtils_DEFINED
 
 #include "SkTypes.h"
+#include "SkMath.h"
 
 /** Similar to memset(), but it assigns a 16bit value into the buffer.
     @param buffer   The memory to have value copied into it
@@ -58,7 +59,31 @@
 }
 
 int         SkUTF8_CountUnichars(const char utf8[]);
-int         SkUTF8_CountUnichars(const char utf8[], size_t byteLength);
+
+/** This function is safe: invalid UTF8 sequences will return -1; */
+int         SkUTF8_CountUnicharsWithError(const char utf8[], size_t byteLength);
+
+/** This function is safe: invalid UTF8 sequences will return 0; */
+inline int  SkUTF8_CountUnichars(const char utf8[], size_t byteLength) {
+    return SkClampPos(SkUTF8_CountUnicharsWithError(utf8, byteLength));
+}
+
+/** This function is safe: invalid UTF8 sequences will return -1
+ *  When -1 is returned, ptr is unchanged.
+ *  Precondition: *ptr < end;
+ */
+SkUnichar SkUTF8_NextUnicharWithError(const char** ptr, const char* end);
+
+/** this version replaces invalid utf-8 sequences with code point U+FFFD. */
+inline SkUnichar SkUTF8_NextUnichar(const char** ptr, const char* end) {
+    SkUnichar val = SkUTF8_NextUnicharWithError(ptr, end);
+    if (val < 0) {
+        *ptr = end;
+        return 0xFFFD;  // REPLACEMENT CHARACTER
+    }
+    return val;
+}
+
 SkUnichar   SkUTF8_ToUnichar(const char utf8[]);
 SkUnichar   SkUTF8_NextUnichar(const char**);
 SkUnichar   SkUTF8_PrevUnichar(const char**);
@@ -99,5 +124,4 @@
     }
     return true;
 }
-
 #endif
diff --git a/src/core/SkXfermode.cpp b/src/core/SkXfermode.cpp
index 9c1ae44..6f470f6 100644
--- a/src/core/SkXfermode.cpp
+++ b/src/core/SkXfermode.cpp
@@ -1001,7 +1001,7 @@
     return nullptr;
 }
 
-sk_sp<GrXPFactory> SkXfermode::asXPFactory() const {
+const GrXPFactory* SkXfermode::asXPFactory() const {
     // This should never be called.
     // TODO: make pure virtual in SkXfermode once Android update lands
     SkASSERT(0);
@@ -1254,15 +1254,15 @@
     return GrXfermodeFragmentProcessor::MakeFromDstProcessor(std::move(dst), fMode);
 }
 
-sk_sp<GrXPFactory> SkProcCoeffXfermode::asXPFactory() const {
+const GrXPFactory* SkProcCoeffXfermode::asXPFactory() const {
     if (CANNOT_USE_COEFF != fSrcCoeff) {
-        sk_sp<GrXPFactory> result(GrPorterDuffXPFactory::Make(fMode));
+        const GrXPFactory* result(GrPorterDuffXPFactory::Get(fMode));
         SkASSERT(result);
         return result;
     }
 
     SkASSERT(GrCustomXfermode::IsSupportedMode(fMode));
-    return GrCustomXfermode::MakeXPFactory(fMode);
+    return GrCustomXfermode::Get(fMode);
 }
 #endif
 
@@ -1469,16 +1469,16 @@
 }
 
 #if SK_SUPPORT_GPU
-sk_sp<GrXPFactory> SkBlendMode_AsXPFactory(SkBlendMode mode) {
+const GrXPFactory* SkBlendMode_AsXPFactory(SkBlendMode mode) {
     const ProcCoeff rec = gProcCoeffs[(int)mode];
     if (CANNOT_USE_COEFF != rec.fSC) {
-        sk_sp<GrXPFactory> result(GrPorterDuffXPFactory::Make(mode));
+        const GrXPFactory* result = GrPorterDuffXPFactory::Get(mode);
         SkASSERT(result);
         return result;
     }
 
     SkASSERT(GrCustomXfermode::IsSupportedMode(mode));
-    return GrCustomXfermode::MakeXPFactory(mode);
+    return GrCustomXfermode::Get(mode);
 }
 #endif
 
diff --git a/src/core/SkXfermodePriv.h b/src/core/SkXfermodePriv.h
index 208925e..2fae2c0 100644
--- a/src/core/SkXfermodePriv.h
+++ b/src/core/SkXfermodePriv.h
@@ -246,7 +246,7 @@
         The xfermode will return a factory for which the caller will get a ref. It is up
         to the caller to install it. XferProcessors cannot use a background texture.
       */
-    virtual sk_sp<GrXPFactory> asXPFactory() const;
+    virtual const GrXPFactory* asXPFactory() const;
 #endif
 
     SK_TO_STRING_PUREVIRT()
diff --git a/src/core/SkXfermode_proccoeff.h b/src/core/SkXfermode_proccoeff.h
index 8a7b62f..3724532 100644
--- a/src/core/SkXfermode_proccoeff.h
+++ b/src/core/SkXfermode_proccoeff.h
@@ -46,7 +46,7 @@
 #if SK_SUPPORT_GPU
     sk_sp<GrFragmentProcessor> makeFragmentProcessorForImageFilter(
                                                         sk_sp<GrFragmentProcessor>) const override;
-    sk_sp<GrXPFactory> asXPFactory() const override;
+    const GrXPFactory* asXPFactory() const override;
 #endif
 
     SK_TO_STRING_OVERRIDE()
diff --git a/src/effects/SkAlphaThresholdFilter.cpp b/src/effects/SkAlphaThresholdFilter.cpp
index 81416e2..82daa3c 100644
--- a/src/effects/SkAlphaThresholdFilter.cpp
+++ b/src/effects/SkAlphaThresholdFilter.cpp
@@ -106,15 +106,15 @@
         return nullptr;
     }
 
-    GrPaint grPaint;
-    grPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+    GrPaint paint;
+    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
     SkRegion::Iterator iter(fRegion);
     rtContext->clear(nullptr, 0x0, true);
 
     GrFixedClip clip(SkIRect::MakeWH(bounds.width(), bounds.height()));
     while (!iter.done()) {
         SkRect rect = SkRect::Make(iter.rect());
-        rtContext->drawRect(clip, grPaint, GrAA::kNo, inMatrix, rect);
+        rtContext->drawRect(clip, std::move(paint), GrAA::kNo, inMatrix, rect);
         iter.next();
     }
 
diff --git a/src/effects/SkArithmeticImageFilter.cpp b/src/effects/SkArithmeticImageFilter.cpp
new file mode 100644
index 0000000..1e4f589
--- /dev/null
+++ b/src/effects/SkArithmeticImageFilter.cpp
@@ -0,0 +1,515 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkArithmeticImageFilter.h"
+#include "SkArithmeticModePriv.h"
+#include "SkCanvas.h"
+#include "SkNx.h"
+#include "SkReadBuffer.h"
+#include "SkSpecialImage.h"
+#include "SkSpecialSurface.h"
+#include "SkWriteBuffer.h"
+#include "SkXfermodeImageFilter.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrRenderTargetContext.h"
+#include "GrTextureProxy.h"
+#include "SkGr.h"
+#include "SkGrPriv.h"
+#include "effects/GrConstColorProcessor.h"
+#include "effects/GrTextureDomain.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramDataManager.h"
+#include "glsl/GrGLSLUniformHandler.h"
+#endif
+
+namespace {
+class ArithmeticImageFilterImpl : public SkImageFilter {
+public:
+    ArithmeticImageFilterImpl(float k1, float k2, float k3, float k4, bool enforcePMColor,
+                              sk_sp<SkImageFilter> inputs[2], const CropRect* cropRect)
+            : INHERITED(inputs, 2, cropRect), fK{k1, k2, k3, k4}, fEnforcePMColor(enforcePMColor) {}
+
+    SK_TO_STRING_OVERRIDE()
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(ArithmeticImageFilterImpl)
+
+protected:
+    sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
+                                        SkIPoint* offset) const override;
+
+#if SK_SUPPORT_GPU
+    sk_sp<SkSpecialImage> filterImageGPU(SkSpecialImage* source,
+                                         sk_sp<SkSpecialImage> background,
+                                         const SkIPoint& backgroundOffset,
+                                         sk_sp<SkSpecialImage> foreground,
+                                         const SkIPoint& foregroundOffset,
+                                         const SkIRect& bounds,
+                                         const OutputProperties& outputProperties) const;
+#endif
+
+    void flatten(SkWriteBuffer& buffer) const override {
+        this->INHERITED::flatten(buffer);
+        for (int i = 0; i < 4; ++i) {
+            buffer.writeScalar(fK[i]);
+        }
+        buffer.writeBool(fEnforcePMColor);
+    }
+
+    void drawForeground(SkCanvas* canvas, SkSpecialImage*, const SkIRect&) const;
+
+private:
+    const float fK[4];
+    const bool fEnforcePMColor;
+
+    friend class ::SkArithmeticImageFilter;
+
+    typedef SkImageFilter INHERITED;
+};
+}
+
+sk_sp<SkFlattenable> ArithmeticImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
+    SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2);
+    float k[4];
+    for (int i = 0; i < 4; ++i) {
+        k[i] = buffer.readScalar();
+    }
+    const bool enforcePMColor = buffer.readBool();
+    return SkArithmeticImageFilter::Make(k[0], k[1], k[2], k[3], enforcePMColor, common.getInput(0),
+                                         common.getInput(1), &common.cropRect());
+}
+
+static Sk4f pin(float min, const Sk4f& val, float max) {
+    return Sk4f::Max(min, Sk4f::Min(val, max));
+}
+
+template <bool EnforcePMColor>
+void arith_span(const float k[], SkPMColor dst[], const SkPMColor src[], int count) {
+    const Sk4f k1 = k[0] * (1/255.0f),
+               k2 = k[1],
+               k3 = k[2],
+               k4 = k[3] * 255.0f + 0.5f;
+
+    for (int i = 0; i < count; i++) {
+        Sk4f s = SkNx_cast<float>(Sk4b::Load(src+i)),
+             d = SkNx_cast<float>(Sk4b::Load(dst+i)),
+             r = pin(0, k1*s*d + k2*s + k3*d + k4, 255);
+        if (EnforcePMColor) {
+            Sk4f a = SkNx_shuffle<3,3,3,3>(r);
+            r = Sk4f::Min(a, r);
+        }
+        SkNx_cast<uint8_t>(r).store(dst+i);
+    }
+}
+
+// apply mode to src==transparent (0)
+template<bool EnforcePMColor> void arith_transparent(const float k[], SkPMColor dst[], int count) {
+    const Sk4f k3 = k[2],
+               k4 = k[3] * 255.0f + 0.5f;
+
+    for (int i = 0; i < count; i++) {
+        Sk4f d = SkNx_cast<float>(Sk4b::Load(dst+i)),
+             r = pin(0, k3*d + k4, 255);
+        if (EnforcePMColor) {
+            Sk4f a = SkNx_shuffle<3,3,3,3>(r);
+            r = Sk4f::Min(a, r);
+        }
+        SkNx_cast<uint8_t>(r).store(dst+i);
+    }
+}
+
+static bool intersect(SkPixmap* dst, SkPixmap* src, int srcDx, int srcDy) {
+    SkIRect dstR = SkIRect::MakeWH(dst->width(), dst->height());
+    SkIRect srcR = SkIRect::MakeXYWH(srcDx, srcDy, src->width(), src->height());
+    SkIRect sect;
+    if (!sect.intersect(dstR, srcR)) {
+        return false;
+    }
+    *dst = SkPixmap(dst->info().makeWH(sect.width(), sect.height()),
+                    dst->addr(sect.fLeft, sect.fTop),
+                    dst->rowBytes());
+    *src = SkPixmap(src->info().makeWH(sect.width(), sect.height()),
+                    src->addr(SkTMax(0, -srcDx), SkTMax(0, -srcDy)),
+                    src->rowBytes());
+    return true;
+}
+
+sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::onFilterImage(SkSpecialImage* source,
+                                                               const Context& ctx,
+                                                               SkIPoint* offset) const {
+    SkIPoint backgroundOffset = SkIPoint::Make(0, 0);
+    sk_sp<SkSpecialImage> background(this->filterInput(0, source, ctx, &backgroundOffset));
+
+    SkIPoint foregroundOffset = SkIPoint::Make(0, 0);
+    sk_sp<SkSpecialImage> foreground(this->filterInput(1, source, ctx, &foregroundOffset));
+
+    SkIRect foregroundBounds = SkIRect::EmptyIRect();
+    if (foreground) {
+        foregroundBounds = SkIRect::MakeXYWH(foregroundOffset.x(), foregroundOffset.y(),
+                                             foreground->width(), foreground->height());
+    }
+
+    SkIRect srcBounds = SkIRect::EmptyIRect();
+    if (background) {
+        srcBounds = SkIRect::MakeXYWH(backgroundOffset.x(), backgroundOffset.y(),
+                                      background->width(), background->height());
+    }
+
+    srcBounds.join(foregroundBounds);
+    if (srcBounds.isEmpty()) {
+        return nullptr;
+    }
+
+    SkIRect bounds;
+    if (!this->applyCropRect(ctx, srcBounds, &bounds)) {
+        return nullptr;
+    }
+
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
+
+#if SK_SUPPORT_GPU
+    if (source->isTextureBacked()) {
+        return this->filterImageGPU(source, background, backgroundOffset, foreground,
+                                    foregroundOffset, bounds, ctx.outputProperties());
+    }
+#endif
+
+    sk_sp<SkSpecialSurface> surf(source->makeSurface(ctx.outputProperties(), bounds.size()));
+    if (!surf) {
+        return nullptr;
+    }
+
+    SkCanvas* canvas = surf->getCanvas();
+    SkASSERT(canvas);
+
+    canvas->clear(0x0);  // can't count on background to fully clear the background
+    canvas->translate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top()));
+
+    if (background) {
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kSrc);
+        background->draw(canvas, SkIntToScalar(backgroundOffset.fX),
+                         SkIntToScalar(backgroundOffset.fY), &paint);
+    }
+
+    this->drawForeground(canvas, foreground.get(), foregroundBounds);
+
+    return surf->makeImageSnapshot();
+}
+
+#if SK_SUPPORT_GPU
+
+namespace {
+class ArithmeticFP : public GrFragmentProcessor {
+public:
+    static sk_sp<GrFragmentProcessor> Make(float k1, float k2, float k3, float k4,
+                                           bool enforcePMColor, sk_sp<GrFragmentProcessor> dst) {
+        return sk_sp<GrFragmentProcessor>(
+                new ArithmeticFP(k1, k2, k3, k4, enforcePMColor, std::move(dst)));
+    }
+
+    ~ArithmeticFP() override {}
+
+    const char* name() const override { return "Arithmetic"; }
+
+    SkString dumpInfo() const override {
+        SkString str;
+        str.appendf("K1: %.2f K2: %.2f K3: %.2f K4: %.2f", fK1, fK2, fK3, fK4);
+        return str;
+    }
+
+    float k1() const { return fK1; }
+    float k2() const { return fK2; }
+    float k3() const { return fK3; }
+    float k4() const { return fK4; }
+    bool enforcePMColor() const { return fEnforcePMColor; }
+
+private:
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
+        class GLSLFP : public GrGLSLFragmentProcessor {
+        public:
+            void emitCode(EmitArgs& args) override {
+                const ArithmeticFP& arith = args.fFp.cast<ArithmeticFP>();
+
+                GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+                SkString dstColor("dstColor");
+                this->emitChild(0, nullptr, &dstColor, args);
+
+                fKUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kVec4f_GrSLType,
+                                                         kDefault_GrSLPrecision, "k");
+                const char* kUni = args.fUniformHandler->getUniformCStr(fKUni);
+
+                // We don't try to optimize for this case at all
+                if (!args.fInputColor) {
+                    fragBuilder->codeAppend("const vec4 src = vec4(1);");
+                } else {
+                    fragBuilder->codeAppendf("vec4 src = %s;", args.fInputColor);
+                }
+
+                fragBuilder->codeAppendf("vec4 dst = %s;", dstColor.c_str());
+                fragBuilder->codeAppendf("%s = %s.x * src * dst + %s.y * src + %s.z * dst + %s.w;",
+                                         args.fOutputColor, kUni, kUni, kUni, kUni);
+                fragBuilder->codeAppendf("%s = clamp(%s, 0.0, 1.0);\n", args.fOutputColor,
+                                         args.fOutputColor);
+                if (arith.fEnforcePMColor) {
+                    fragBuilder->codeAppendf("%s.rgb = min(%s.rgb, %s.a);", args.fOutputColor,
+                                             args.fOutputColor, args.fOutputColor);
+                }
+            }
+
+        protected:
+            void onSetData(const GrGLSLProgramDataManager& pdman,
+                           const GrProcessor& proc) override {
+                const ArithmeticFP& arith = proc.cast<ArithmeticFP>();
+                pdman.set4f(fKUni, arith.k1(), arith.k2(), arith.k3(), arith.k4());
+            }
+
+        private:
+            GrGLSLProgramDataManager::UniformHandle fKUni;
+        };
+        return new GLSLFP;
+    }
+
+    void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
+        b->add32(fEnforcePMColor ? 1 : 0);
+    }
+
+    bool onIsEqual(const GrFragmentProcessor& fpBase) const override {
+        const ArithmeticFP& fp = fpBase.cast<ArithmeticFP>();
+        return fK1 == fp.fK1 && fK2 == fp.fK2 && fK3 == fp.fK3 && fK4 == fp.fK4 &&
+               fEnforcePMColor == fp.fEnforcePMColor;
+    }
+
+    void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
+        // TODO: optimize this
+        inout->setToUnknown(GrInvariantOutput::kWill_ReadInput);
+    }
+
+    ArithmeticFP(float k1, float k2, float k3, float k4, bool enforcePMColor,
+                 sk_sp<GrFragmentProcessor> dst)
+            : fK1(k1), fK2(k2), fK3(k3), fK4(k4), fEnforcePMColor(enforcePMColor) {
+        this->initClassID<ArithmeticFP>();
+        SkASSERT(dst);
+        SkDEBUGCODE(int dstIndex =) this->registerChildProcessor(std::move(dst));
+        SkASSERT(0 == dstIndex);
+    }
+
+    float fK1, fK2, fK3, fK4;
+    bool fEnforcePMColor;
+
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
+    typedef GrFragmentProcessor INHERITED;
+};
+}
+
+sk_sp<GrFragmentProcessor> ArithmeticFP::TestCreate(GrProcessorTestData* d) {
+    float k1 = d->fRandom->nextF();
+    float k2 = d->fRandom->nextF();
+    float k3 = d->fRandom->nextF();
+    float k4 = d->fRandom->nextF();
+    bool enforcePMColor = d->fRandom->nextBool();
+
+    sk_sp<GrFragmentProcessor> dst(GrProcessorUnitTest::MakeChildFP(d));
+    return ArithmeticFP::Make(k1, k2, k3, k4, enforcePMColor, std::move(dst));
+}
+
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(ArithmeticFP);
+
+sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU(
+        SkSpecialImage* source,
+        sk_sp<SkSpecialImage>
+                background,
+        const SkIPoint& backgroundOffset,
+        sk_sp<SkSpecialImage>
+                foreground,
+        const SkIPoint& foregroundOffset,
+        const SkIRect& bounds,
+        const OutputProperties& outputProperties) const {
+    SkASSERT(source->isTextureBacked());
+
+    GrContext* context = source->getContext();
+
+    sk_sp<GrTexture> backgroundTex, foregroundTex;
+
+    if (background) {
+        backgroundTex = background->asTextureRef(context);
+    }
+
+    if (foreground) {
+        foregroundTex = foreground->asTextureRef(context);
+    }
+
+    GrPaint paint;
+    sk_sp<GrFragmentProcessor> bgFP;
+
+    if (backgroundTex) {
+        SkMatrix backgroundMatrix;
+        backgroundMatrix.setIDiv(backgroundTex->width(), backgroundTex->height());
+        backgroundMatrix.preTranslate(-SkIntToScalar(backgroundOffset.fX),
+                                      -SkIntToScalar(backgroundOffset.fY));
+        sk_sp<GrColorSpaceXform> bgXform =
+                GrColorSpaceXform::Make(background->getColorSpace(), outputProperties.colorSpace());
+        bgFP = GrTextureDomainEffect::Make(
+                backgroundTex.get(), std::move(bgXform), backgroundMatrix,
+                GrTextureDomain::MakeTexelDomain(background->subset()),
+                GrTextureDomain::kDecal_Mode, GrSamplerParams::kNone_FilterMode);
+    } else {
+        bgFP = GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
+                                           GrConstColorProcessor::kIgnore_InputMode);
+    }
+
+    if (foregroundTex) {
+        SkMatrix foregroundMatrix;
+        foregroundMatrix.setIDiv(foregroundTex->width(), foregroundTex->height());
+        foregroundMatrix.preTranslate(-SkIntToScalar(foregroundOffset.fX),
+                                      -SkIntToScalar(foregroundOffset.fY));
+        sk_sp<GrColorSpaceXform> fgXform =
+                GrColorSpaceXform::Make(foreground->getColorSpace(), outputProperties.colorSpace());
+        sk_sp<GrFragmentProcessor> foregroundFP;
+
+        foregroundFP = GrTextureDomainEffect::Make(
+                foregroundTex.get(), std::move(fgXform), foregroundMatrix,
+                GrTextureDomain::MakeTexelDomain(foreground->subset()),
+                GrTextureDomain::kDecal_Mode, GrSamplerParams::kNone_FilterMode);
+
+        paint.addColorFragmentProcessor(std::move(foregroundFP));
+
+        sk_sp<GrFragmentProcessor> xferFP =
+                ArithmeticFP::Make(fK[0], fK[1], fK[2], fK[3], fEnforcePMColor, std::move(bgFP));
+
+        // A null 'xferFP' here means kSrc_Mode was used in which case we can just proceed
+        if (xferFP) {
+            paint.addColorFragmentProcessor(std::move(xferFP));
+        }
+    } else {
+        paint.addColorFragmentProcessor(std::move(bgFP));
+    }
+
+    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+
+    sk_sp<GrRenderTargetContext> renderTargetContext(context->makeDeferredRenderTargetContext(
+            SkBackingFit::kApprox, bounds.width(), bounds.height(),
+            GrRenderableConfigForColorSpace(outputProperties.colorSpace()),
+            sk_ref_sp(outputProperties.colorSpace())));
+    if (!renderTargetContext) {
+        return nullptr;
+    }
+    paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
+
+    SkMatrix matrix;
+    matrix.setTranslate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top()));
+    renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix,
+                                  SkRect::Make(bounds));
+
+    return SkSpecialImage::MakeDeferredFromGpu(context,
+                                               SkIRect::MakeWH(bounds.width(), bounds.height()),
+                                               kNeedNewImageUniqueID_SpecialImage,
+                                               sk_ref_sp(renderTargetContext->asDeferredTexture()),
+                                               sk_ref_sp(renderTargetContext->getColorSpace()));
+}
+#endif
+
+void ArithmeticImageFilterImpl::drawForeground(SkCanvas* canvas, SkSpecialImage* img,
+                                               const SkIRect& fgBounds) const {
+    SkPixmap dst;
+    if (!canvas->peekPixels(&dst)) {
+        return;
+    }
+
+    const SkMatrix& ctm = canvas->getTotalMatrix();
+    SkASSERT(ctm.getType() <= SkMatrix::kTranslate_Mask);
+    const int dx = SkScalarRoundToInt(ctm.getTranslateX());
+    const int dy = SkScalarRoundToInt(ctm.getTranslateY());
+
+    if (img) {
+        SkBitmap srcBM;
+        SkPixmap src;
+        if (!img->getROPixels(&srcBM)) {
+            return;
+        }
+        srcBM.lockPixels();
+        if (!srcBM.peekPixels(&src)) {
+            return;
+        }
+
+        auto proc = fEnforcePMColor ? arith_span<true> : arith_span<false>;
+        SkPixmap tmpDst = dst;
+        if (intersect(&tmpDst, &src, fgBounds.fLeft + dx, fgBounds.fTop + dy)) {
+            for (int y = 0; y < tmpDst.height(); ++y) {
+                proc(fK, tmpDst.writable_addr32(0, y), src.addr32(0, y), tmpDst.width());
+            }
+        }
+    }
+
+    // Now apply the mode with transparent-color to the outside of the fg image
+    SkRegion outside(SkIRect::MakeWH(dst.width(), dst.height()));
+    outside.op(fgBounds.makeOffset(dx, dy), SkRegion::kDifference_Op);
+    auto proc = fEnforcePMColor ? arith_transparent<true> : arith_transparent<false>;
+    for (SkRegion::Iterator iter(outside); !iter.done(); iter.next()) {
+        const SkIRect r = iter.rect();
+        for (int y = r.fTop; y < r.fBottom; ++y) {
+            proc(fK, dst.writable_addr32(r.fLeft, y), r.width());
+        }
+    }
+}
+
+#ifndef SK_IGNORE_TO_STRING
+void ArithmeticImageFilterImpl::toString(SkString* str) const {
+    str->appendf("SkArithmeticImageFilter: (");
+    str->appendf("K[]: (%f %f %f %f)", fK[0], fK[1], fK[2], fK[3]);
+    if (this->getInput(0)) {
+        str->appendf("foreground: (");
+        this->getInput(0)->toString(str);
+        str->appendf(")");
+    }
+    if (this->getInput(1)) {
+        str->appendf("background: (");
+        this->getInput(1)->toString(str);
+        str->appendf(")");
+    }
+    str->append(")");
+}
+#endif
+
+sk_sp<SkImageFilter> SkArithmeticImageFilter::Make(float k1, float k2, float k3, float k4,
+                                                   bool enforcePMColor,
+                                                   sk_sp<SkImageFilter> background,
+                                                   sk_sp<SkImageFilter> foreground,
+                                                   const SkImageFilter::CropRect* crop) {
+    if (!SkScalarIsFinite(k1) || !SkScalarIsFinite(k2) || !SkScalarIsFinite(k3) ||
+        !SkScalarIsFinite(k4)) {
+        return nullptr;
+    }
+
+    // are we nearly some other "std" mode?
+    int mode = -1;  // illegal mode
+    if (SkScalarNearlyZero(k1) && SkScalarNearlyEqual(k2, SK_Scalar1) && SkScalarNearlyZero(k3) &&
+        SkScalarNearlyZero(k4)) {
+        mode = (int)SkBlendMode::kSrc;
+    } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) &&
+               SkScalarNearlyEqual(k3, SK_Scalar1) && SkScalarNearlyZero(k4)) {
+        mode = (int)SkBlendMode::kDst;
+    } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) && SkScalarNearlyZero(k3) &&
+               SkScalarNearlyZero(k4)) {
+        mode = (int)SkBlendMode::kClear;
+    }
+    if (mode >= 0) {
+        return SkXfermodeImageFilter::Make((SkBlendMode)mode, std::move(background),
+                                           std::move(foreground), crop);
+    }
+
+    sk_sp<SkImageFilter> inputs[2] = {std::move(background), std::move(foreground)};
+    return sk_sp<SkImageFilter>(
+            new ArithmeticImageFilterImpl(k1, k2, k3, k4, enforcePMColor, inputs, crop));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkArithmeticImageFilter)
+    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(ArithmeticImageFilterImpl)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/src/effects/SkArithmeticMode.cpp b/src/effects/SkArithmeticMode.cpp
index 62595cf..d604a12 100644
--- a/src/effects/SkArithmeticMode.cpp
+++ b/src/effects/SkArithmeticMode.cpp
@@ -6,17 +6,11 @@
  */
 
 #include "SkArithmeticModePriv.h"
-#include "SkColorPriv.h"
-#include "SkNx.h"
-#include "SkRasterPipeline.h"
 #include "SkReadBuffer.h"
-#include "SkString.h"
-#include "SkUnPreMultiply.h"
-#include "SkWriteBuffer.h"
-#if SK_SUPPORT_GPU
-#include "SkArithmeticMode_gpu.h"
-#endif
 
+// This class only exists to unflatten instances that were serialized into old pictures as part of
+// SkXfermodeImageFilter before the advent of SkBlendMode. Those image filters will now be
+// transformed to SkArithmeticImageFilter which does not use this class in its implementation.
 class SkArithmeticMode_scalar : public SkXfermode {
 public:
     SkArithmeticMode_scalar(SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4,
@@ -28,17 +22,15 @@
         fEnforcePMColor = enforcePMColor;
     }
 
-    void xfer32(SkPMColor[], const SkPMColor[], int count, const SkAlpha[]) const override;
+    void xfer32(SkPMColor[], const SkPMColor[], int count, const SkAlpha[]) const override {
+        SkFAIL("This should never be called.");
+    }
 
     SK_TO_STRING_OVERRIDE()
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkArithmeticMode_scalar)
 
-#if SK_SUPPORT_GPU
-    sk_sp<GrFragmentProcessor> makeFragmentProcessorForImageFilter(
-                                                sk_sp<GrFragmentProcessor> dst) const override;
-    sk_sp<GrXPFactory> asXPFactory() const override;
-#endif
-
+    // This is used to extract the arithmetic params into an SkArithmeticImageFilter. Afterwards,
+    // this object is destroyed and arithemtic blending is implemented directly in the image filter.
     bool isArithmetic(SkArithmeticParams* params) const override {
         if (params) {
             memcpy(params->fK, fK, 4 * sizeof(float));
@@ -48,13 +40,7 @@
     }
 
 private:
-    void flatten(SkWriteBuffer& buffer) const override {
-        buffer.writeScalar(fK[0]);
-        buffer.writeScalar(fK[1]);
-        buffer.writeScalar(fK[2]);
-        buffer.writeScalar(fK[3]);
-        buffer.writeBool(fEnforcePMColor);
-    }
+    void flatten(SkWriteBuffer& buffer) const override { SkFAIL("This shouild never be called."); }
 
     SkScalar fK[4];
     bool fEnforcePMColor;
@@ -73,48 +59,9 @@
     return SkArithmeticMode::Make(k1, k2, k3, k4, enforcePMColor);
 }
 
-void SkArithmeticMode_scalar::xfer32(SkPMColor dst[], const SkPMColor src[],
-                                 int count, const SkAlpha aaCoverage[]) const {
-    const Sk4f k1 = fK[0] * (1/255.0f),
-               k2 = fK[1],
-               k3 = fK[2],
-               k4 = fK[3] * 255.0f + 0.5f;
-
-    auto pin = [](float min, const Sk4f& val, float max) {
-        return Sk4f::Max(min, Sk4f::Min(val, max));
-    };
-
-    for (int i = 0; i < count; i++) {
-        if (aaCoverage && aaCoverage[i] == 0) {
-            continue;
-        }
-
-        Sk4f s = SkNx_cast<float>(Sk4b::Load(src+i)),
-             d = SkNx_cast<float>(Sk4b::Load(dst+i)),
-             r = pin(0, k1*s*d + k2*s + k3*d + k4, 255);
-
-        if (fEnforcePMColor) {
-            Sk4f a = SkNx_shuffle<3,3,3,3>(r);
-            r = Sk4f::Min(a, r);
-        }
-
-        if (aaCoverage && aaCoverage[i] != 255) {
-            Sk4f c = aaCoverage[i] * (1/255.0f);
-            r = d + (r-d)*c;
-        }
-
-        SkNx_cast<uint8_t>(r).store(dst+i);
-    }
-}
-
 #ifndef SK_IGNORE_TO_STRING
 void SkArithmeticMode_scalar::toString(SkString* str) const {
-    str->append("SkArithmeticMode_scalar: ");
-    for (int i = 0; i < 4; ++i) {
-        str->appendScalar(fK[i]);
-        str->append(" ");
-    }
-    str->appendS32(fEnforcePMColor ? 1 : 0);
+    SkFAIL("This should never be called.");
 }
 #endif
 
@@ -132,30 +79,6 @@
     return sk_make_sp<SkArithmeticMode_scalar>(k1, k2, k3, k4, enforcePMColor);
 }
 
-
-//////////////////////////////////////////////////////////////////////////////
-
-#if SK_SUPPORT_GPU
-sk_sp<GrFragmentProcessor> SkArithmeticMode_scalar::makeFragmentProcessorForImageFilter(
-                                                            sk_sp<GrFragmentProcessor> dst) const {
-    return GrArithmeticFP::Make(SkScalarToFloat(fK[0]),
-                                SkScalarToFloat(fK[1]),
-                                SkScalarToFloat(fK[2]),
-                                SkScalarToFloat(fK[3]),
-                                fEnforcePMColor,
-                                std::move(dst));
-}
-
-sk_sp<GrXPFactory> SkArithmeticMode_scalar::asXPFactory() const {
-    return GrArithmeticXPFactory::Make(SkScalarToFloat(fK[0]),
-                                       SkScalarToFloat(fK[1]),
-                                       SkScalarToFloat(fK[2]),
-                                       SkScalarToFloat(fK[3]),
-                                       fEnforcePMColor);
-}
-
-#endif
-
 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkArithmeticMode)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkArithmeticMode_scalar)
 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/src/effects/SkArithmeticModePriv.h b/src/effects/SkArithmeticModePriv.h
index ff7f357..4c13a81 100644
--- a/src/effects/SkArithmeticModePriv.h
+++ b/src/effects/SkArithmeticModePriv.h
@@ -18,6 +18,8 @@
     bool fEnforcePMColor;
 };
 
+// This only exists to unflatten instances that were serialized into old pictures as part of
+// SkXfermodeImageFilter before the advent of SkBlendMode.
 class SK_API SkArithmeticMode {
 public:
     /**
diff --git a/src/effects/SkArithmeticMode_gpu.cpp b/src/effects/SkArithmeticMode_gpu.cpp
deleted file mode 100644
index 9f08736..0000000
--- a/src/effects/SkArithmeticMode_gpu.cpp
+++ /dev/null
@@ -1,295 +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 "SkArithmeticMode_gpu.h"
-
-#if SK_SUPPORT_GPU
-#include "GrContext.h"
-#include "GrFragmentProcessor.h"
-#include "GrInvariantOutput.h"
-#include "GrProcessor.h"
-#include "GrTexture.h"
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramDataManager.h"
-#include "glsl/GrGLSLUniformHandler.h"
-#include "glsl/GrGLSLXferProcessor.h"
-
-static void add_arithmetic_code(GrGLSLFragmentBuilder* fragBuilder,
-                                const char* srcColor,
-                                const char* dstColor,
-                                const char* outputColor,
-                                const char* kUni,
-                                bool enforcePMColor) {
-    // We don't try to optimize for this case at all
-    if (nullptr == srcColor) {
-        fragBuilder->codeAppend("const vec4 src = vec4(1);");
-    } else {
-        fragBuilder->codeAppendf("vec4 src = %s;", srcColor);
-    }
-
-    fragBuilder->codeAppendf("vec4 dst = %s;", dstColor);
-    fragBuilder->codeAppendf("%s = %s.x * src * dst + %s.y * src + %s.z * dst + %s.w;",
-                             outputColor, kUni, kUni, kUni, kUni);
-    fragBuilder->codeAppendf("%s = clamp(%s, 0.0, 1.0);\n", outputColor, outputColor);
-    if (enforcePMColor) {
-        fragBuilder->codeAppendf("%s.rgb = min(%s.rgb, %s.a);",
-                                 outputColor, outputColor, outputColor);
-    }
-}
-
-class GLArithmeticFP : public GrGLSLFragmentProcessor {
-public:
-    void emitCode(EmitArgs& args) override {
-        const GrArithmeticFP& arith = args.fFp.cast<GrArithmeticFP>();
-
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        SkString dstColor("dstColor");
-        this->emitChild(0, nullptr, &dstColor, args);
-
-        fKUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                 kVec4f_GrSLType, kDefault_GrSLPrecision,
-                                                 "k");
-        const char* kUni = args.fUniformHandler->getUniformCStr(fKUni);
-
-        add_arithmetic_code(fragBuilder,
-                            args.fInputColor,
-                            dstColor.c_str(),
-                            args.fOutputColor,
-                            kUni,
-                            arith.enforcePMColor());
-    }
-
-    static void GenKey(const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) {
-        const GrArithmeticFP& arith = proc.cast<GrArithmeticFP>();
-        uint32_t key = arith.enforcePMColor() ? 1 : 0;
-        b->add32(key);
-    }
-
-protected:
-    void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override {
-        const GrArithmeticFP& arith = proc.cast<GrArithmeticFP>();
-        pdman.set4f(fKUni, arith.k1(), arith.k2(), arith.k3(), arith.k4());
-    }
-
-private:
-    GrGLSLProgramDataManager::UniformHandle fKUni;
-
-    typedef GrGLSLFragmentProcessor INHERITED;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-GrArithmeticFP::GrArithmeticFP(float k1, float k2, float k3, float k4, bool enforcePMColor,
-                               sk_sp<GrFragmentProcessor> dst)
-  : fK1(k1), fK2(k2), fK3(k3), fK4(k4), fEnforcePMColor(enforcePMColor) {
-    this->initClassID<GrArithmeticFP>();
-
-    SkASSERT(dst);
-    SkDEBUGCODE(int dstIndex = )this->registerChildProcessor(std::move(dst));
-    SkASSERT(0 == dstIndex);
-}
-
-void GrArithmeticFP::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                           GrProcessorKeyBuilder* b) const {
-    GLArithmeticFP::GenKey(*this, caps, b);
-}
-
-GrGLSLFragmentProcessor* GrArithmeticFP::onCreateGLSLInstance() const {
-    return new GLArithmeticFP;
-}
-
-bool GrArithmeticFP::onIsEqual(const GrFragmentProcessor& fpBase) const {
-    const GrArithmeticFP& fp = fpBase.cast<GrArithmeticFP>();
-    return fK1 == fp.fK1 &&
-           fK2 == fp.fK2 &&
-           fK3 == fp.fK3 &&
-           fK4 == fp.fK4 &&
-           fEnforcePMColor == fp.fEnforcePMColor;
-}
-
-void GrArithmeticFP::onComputeInvariantOutput(GrInvariantOutput* inout) const {
-    // TODO: optimize this
-    inout->setToUnknown(GrInvariantOutput::kWill_ReadInput);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-sk_sp<GrFragmentProcessor> GrArithmeticFP::TestCreate(GrProcessorTestData* d) {
-    float k1 = d->fRandom->nextF();
-    float k2 = d->fRandom->nextF();
-    float k3 = d->fRandom->nextF();
-    float k4 = d->fRandom->nextF();
-    bool enforcePMColor = d->fRandom->nextBool();
-
-    sk_sp<GrFragmentProcessor> dst(GrProcessorUnitTest::MakeChildFP(d));
-    return GrArithmeticFP::Make(k1, k2, k3, k4, enforcePMColor, std::move(dst));
-}
-
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrArithmeticFP);
-
-///////////////////////////////////////////////////////////////////////////////
-// Xfer Processor
-///////////////////////////////////////////////////////////////////////////////
-
-class ArithmeticXP : public GrXferProcessor {
-public:
-    ArithmeticXP(const DstTexture*, bool hasMixedSamples,
-                 float k1, float k2, float k3, float k4, bool enforcePMColor);
-
-    const char* name() const override { return "Arithmetic"; }
-
-    GrGLSLXferProcessor* createGLSLInstance() const override;
-
-    float k1() const { return fK1; }
-    float k2() const { return fK2; }
-    float k3() const { return fK3; }
-    float k4() const { return fK4; }
-    bool enforcePMColor() const { return fEnforcePMColor; }
-
-private:
-    GrXferProcessor::OptFlags onGetOptimizations(const GrPipelineAnalysis&,
-                                                 bool doesStencilWrite,
-                                                 GrColor* overrideColor,
-                                                 const GrCaps& caps) const override;
-
-    void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override;
-
-    bool onIsEqual(const GrXferProcessor& xpBase) const override {
-        const ArithmeticXP& xp = xpBase.cast<ArithmeticXP>();
-        if (fK1 != xp.fK1 ||
-            fK2 != xp.fK2 ||
-            fK3 != xp.fK3 ||
-            fK4 != xp.fK4 ||
-            fEnforcePMColor != xp.fEnforcePMColor) {
-            return false;
-        }
-        return true;
-    }
-
-    float                       fK1, fK2, fK3, fK4;
-    bool                        fEnforcePMColor;
-
-    typedef GrXferProcessor INHERITED;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-class GLArithmeticXP : public GrGLSLXferProcessor {
-public:
-    GLArithmeticXP(const ArithmeticXP& arithmeticXP)
-        : fEnforcePMColor(arithmeticXP.enforcePMColor()) {
-    }
-
-    ~GLArithmeticXP() override {}
-
-    static void GenKey(const GrProcessor& processor, const GrShaderCaps& caps,
-                       GrProcessorKeyBuilder* b) {
-        const ArithmeticXP& arith = processor.cast<ArithmeticXP>();
-        uint32_t key = arith.enforcePMColor() ? 1 : 0;
-        b->add32(key);
-    }
-
-private:
-    void emitBlendCodeForDstRead(GrGLSLXPFragmentBuilder* fragBuilder,
-                                 GrGLSLUniformHandler* uniformHandler,
-                                 const char* srcColor,
-                                 const char* srcCoverage,
-                                 const char* dstColor,
-                                 const char* outColor,
-                                 const char* outColorSecondary,
-                                 const GrXferProcessor& proc) override {
-        fKUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                           kVec4f_GrSLType, kDefault_GrSLPrecision,
-                                           "k");
-        const char* kUni = uniformHandler->getUniformCStr(fKUni);
-
-        add_arithmetic_code(fragBuilder, srcColor, dstColor, outColor, kUni, fEnforcePMColor);
-
-        // Apply coverage.
-        INHERITED::DefaultCoverageModulation(fragBuilder, srcCoverage, dstColor, outColor,
-                                             outColorSecondary, proc);
-    }
-
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrXferProcessor& processor) override {
-        const ArithmeticXP& arith = processor.cast<ArithmeticXP>();
-        pdman.set4f(fKUni, arith.k1(), arith.k2(), arith.k3(), arith.k4());
-        fEnforcePMColor = arith.enforcePMColor();
-    }
-
-    GrGLSLProgramDataManager::UniformHandle fKUni;
-    bool fEnforcePMColor;
-
-    typedef GrGLSLXferProcessor INHERITED;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-ArithmeticXP::ArithmeticXP(const DstTexture* dstTexture, bool hasMixedSamples,
-                           float k1, float k2, float k3, float k4, bool enforcePMColor)
-    : INHERITED(dstTexture, true, hasMixedSamples)
-    , fK1(k1)
-    , fK2(k2)
-    , fK3(k3)
-    , fK4(k4)
-    , fEnforcePMColor(enforcePMColor) {
-    this->initClassID<ArithmeticXP>();
-}
-
-void ArithmeticXP::onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const {
-    GLArithmeticXP::GenKey(*this, caps, b);
-}
-
-GrGLSLXferProcessor* ArithmeticXP::createGLSLInstance() const { return new GLArithmeticXP(*this); }
-
-GrXferProcessor::OptFlags ArithmeticXP::onGetOptimizations(const GrPipelineAnalysis&,
-                                                           bool doesStencilWrite,
-                                                           GrColor* overrideColor,
-                                                           const GrCaps& caps) const {
-    return GrXferProcessor::kNone_OptFlags;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-GrArithmeticXPFactory::GrArithmeticXPFactory(float k1, float k2, float k3, float k4,
-                                             bool enforcePMColor)
-    : fK1(k1), fK2(k2), fK3(k3), fK4(k4), fEnforcePMColor(enforcePMColor) {
-    this->initClassID<GrArithmeticXPFactory>();
-}
-
-GrXferProcessor* GrArithmeticXPFactory::onCreateXferProcessor(const GrCaps& caps,
-                                                              const GrPipelineAnalysis&,
-                                                              bool hasMixedSamples,
-                                                              const DstTexture* dstTexture) const {
-    return new ArithmeticXP(dstTexture, hasMixedSamples, fK1, fK2, fK3, fK4, fEnforcePMColor);
-}
-
-
-void GrArithmeticXPFactory::getInvariantBlendedColor(const GrProcOptInfo& colorPOI,
-                                                     InvariantBlendedColor* blendedColor) const {
-    blendedColor->fWillBlendWithDst = true;
-
-    // TODO: We could try to optimize this more. For example if fK1 and fK3 are zero, then we won't
-    // be blending the color with dst at all so we can know what the output color is (up to the
-    // valid color components passed in).
-    blendedColor->fKnownColorFlags = kNone_GrColorComponentFlags;
-}
-
-GR_DEFINE_XP_FACTORY_TEST(GrArithmeticXPFactory);
-
-sk_sp<GrXPFactory> GrArithmeticXPFactory::TestCreate(GrProcessorTestData* d) {
-    float k1 = d->fRandom->nextF();
-    float k2 = d->fRandom->nextF();
-    float k3 = d->fRandom->nextF();
-    float k4 = d->fRandom->nextF();
-    bool enforcePMColor = d->fRandom->nextBool();
-
-    return GrArithmeticXPFactory::Make(k1, k2, k3, k4, enforcePMColor);
-}
-
-#endif
diff --git a/src/effects/SkArithmeticMode_gpu.h b/src/effects/SkArithmeticMode_gpu.h
deleted file mode 100644
index ffd986b..0000000
--- a/src/effects/SkArithmeticMode_gpu.h
+++ /dev/null
@@ -1,120 +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 SkArithmeticMode_gpu_DEFINED
-#define SkArithmeticMode_gpu_DEFINED
-
-#include "SkTypes.h"
-
-#if SK_SUPPORT_GPU
-
-#include "GrCaps.h"
-#include "GrCoordTransform.h"
-#include "GrFragmentProcessor.h"
-#include "GrTypes.h"
-#include "GrXferProcessor.h"
-
-class GrInvariantOutput;
-class GrProcOptInfo;
-class GrTexture;
-
-///////////////////////////////////////////////////////////////////////////////
-// Fragment Processor
-///////////////////////////////////////////////////////////////////////////////
-
-class GrGLArtithmeticFP;
-
-class GrArithmeticFP : public GrFragmentProcessor {
-public:
-    static sk_sp<GrFragmentProcessor> Make(float k1, float k2, float k3, float k4,
-                                           bool enforcePMColor, sk_sp<GrFragmentProcessor> dst) {
-        return sk_sp<GrFragmentProcessor>(new GrArithmeticFP(k1, k2, k3, k4, enforcePMColor,
-                                                             std::move(dst)));
-    }
-
-    ~GrArithmeticFP() override {}
-
-    const char* name() const override { return "Arithmetic"; }
-
-    SkString dumpInfo() const override {
-        SkString str;
-        str.appendf("K1: %.2f K2: %.2f K3: %.2f K4: %.2f", fK1, fK2, fK3, fK4);
-        return str;
-    }
-
-    float k1() const { return fK1; }
-    float k2() const { return fK2; }
-    float k3() const { return fK3; }
-    float k4() const { return fK4; }
-    bool enforcePMColor() const { return fEnforcePMColor; }
-
-private:
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-
-    void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override;
-
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-
-    void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
-
-    GrArithmeticFP(float k1, float k2, float k3, float k4, bool enforcePMColor,
-                   sk_sp<GrFragmentProcessor> dst);
-
-    float                       fK1, fK2, fK3, fK4;
-    bool                        fEnforcePMColor;
-
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
-    typedef GrFragmentProcessor INHERITED;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-// Xfer Processor
-///////////////////////////////////////////////////////////////////////////////
-
-class GrArithmeticXPFactory : public GrXPFactory {
-public:
-    static sk_sp<GrXPFactory> Make(float k1, float k2, float k3, float k4, bool enforcePMColor) {
-        return sk_sp<GrXPFactory>(new GrArithmeticXPFactory(k1, k2, k3, k4, enforcePMColor));
-    }
-
-    void getInvariantBlendedColor(const GrProcOptInfo& colorPOI,
-                                  GrXPFactory::InvariantBlendedColor*) const override;
-
-private:
-    GrArithmeticXPFactory(float k1, float k2, float k3, float k4, bool enforcePMColor);
-
-    GrXferProcessor* onCreateXferProcessor(const GrCaps& caps,
-                                           const GrPipelineAnalysis&,
-                                           bool hasMixedSamples,
-                                           const DstTexture*) const override;
-
-    bool onWillReadDstColor(const GrCaps&, const GrPipelineAnalysis&) const override {
-        return true;
-    }
-
-    bool onIsEqual(const GrXPFactory& xpfBase) const override {
-        const GrArithmeticXPFactory& xpf = xpfBase.cast<GrArithmeticXPFactory>();
-        if (fK1 != xpf.fK1 ||
-            fK2 != xpf.fK2 ||
-            fK3 != xpf.fK3 ||
-            fK4 != xpf.fK4 ||
-            fEnforcePMColor != xpf.fEnforcePMColor) {
-            return false;
-        }
-        return true;
-    }
-
-    GR_DECLARE_XP_FACTORY_TEST;
-
-    float                       fK1, fK2, fK3, fK4;
-    bool                        fEnforcePMColor;
-
-    typedef GrXPFactory INHERITED;
-};
-
-#endif
-#endif
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index f56c273..fa4cb33 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -52,14 +52,14 @@
                           SkRect* maskRect) const override;
     bool directFilterMaskGPU(GrTextureProvider* texProvider,
                              GrRenderTargetContext* renderTargetContext,
-                             GrPaint* grp,
+                             GrPaint&&,
                              const GrClip&,
                              const SkMatrix& viewMatrix,
                              const SkStrokeRec& strokeRec,
                              const SkPath& path) const override;
     bool directFilterRRectMaskGPU(GrContext*,
                                   GrRenderTargetContext* renderTargetContext,
-                                  GrPaint* grp,
+                                  GrPaint&&,
                                   const GrClip&,
                                   const SkMatrix& viewMatrix,
                                   const SkStrokeRec& strokeRec,
@@ -1014,10 +1014,9 @@
                                   sigma);
 }
 
-
 bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider,
                                                GrRenderTargetContext* renderTargetContext,
-                                               GrPaint* grp,
+                                               GrPaint&& paint,
                                                const GrClip& clip,
                                                const SkMatrix& viewMatrix,
                                                const SkStrokeRec& strokeRec,
@@ -1057,15 +1056,14 @@
         return false;
     }
 
-    grp->addCoverageFragmentProcessor(std::move(fp));
-
     SkMatrix inverse;
     if (!viewMatrix.invert(&inverse)) {
         return false;
     }
 
-    renderTargetContext->fillRectWithLocalMatrix(clip, *grp, GrAA::kNo, SkMatrix::I(), rect,
-                                                 inverse);
+    paint.addCoverageFragmentProcessor(std::move(fp));
+    renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
+                                                 rect, inverse);
     return true;
 }
 
@@ -1132,10 +1130,10 @@
             return nullptr;
         }
 
-        GrPaint grPaint;
+        GrPaint paint;
 
         rtc->clear(nullptr, 0x0, true);
-        rtc->drawRRect(GrNoClip(), grPaint, GrAA::kYes, SkMatrix::I(), rrectToDraw,
+        rtc->drawRRect(GrNoClip(), std::move(paint), GrAA::kYes, SkMatrix::I(), rrectToDraw,
                        GrStyle::SimpleFill());
 
         sk_sp<GrTexture> srcTexture(rtc->asTexture());
@@ -1342,7 +1340,7 @@
 
 bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrContext* context,
                                                     GrRenderTargetContext* renderTargetContext,
-                                                    GrPaint* grp,
+                                                    GrPaint&& paint,
                                                     const GrClip& clip,
                                                     const SkMatrix& viewMatrix,
                                                     const SkStrokeRec& strokeRec,
@@ -1381,13 +1379,12 @@
             return false;
         }
 
-        GrPaint newPaint(*grp);
-        newPaint.addCoverageFragmentProcessor(std::move(fp));
+        paint.addCoverageFragmentProcessor(std::move(fp));
 
         SkRect srcProxyRect = srcRRect.rect();
         srcProxyRect.outset(3.0f*fSigma, 3.0f*fSigma);
 
-        renderTargetContext->drawRect(clip, newPaint, GrAA::kNo, viewMatrix, srcProxyRect);
+        renderTargetContext->drawRect(clip, std::move(paint), GrAA::kNo, viewMatrix, srcProxyRect);
         return true;
     }
 
@@ -1397,9 +1394,6 @@
         return false;
     }
 
-    GrPaint newPaint(*grp);
-    newPaint.addCoverageFragmentProcessor(std::move(fp));
-
     if (!this->ignoreXform()) {
         SkRect srcProxyRect = srcRRect.rect();
         srcProxyRect.outset(3.0f*fSigma, 3.0f*fSigma);
@@ -1431,8 +1425,10 @@
             numIndices = 6;
         }
 
-        renderTargetContext->drawVertices(clip, newPaint, viewMatrix, kTriangles_GrPrimitiveType,
-                                          numPoints, points, nullptr, nullptr, indices, numIndices);
+        paint.addCoverageFragmentProcessor(std::move(fp));
+        renderTargetContext->drawVertices(clip, std::move(paint), viewMatrix,
+                                          kTriangles_GrPrimitiveType, numPoints, points, nullptr,
+                                          nullptr, indices, numIndices);
 
     } else {
         SkMatrix inverse;
@@ -1444,9 +1440,9 @@
         SkRect proxyRect = devRRect.rect();
         proxyRect.outset(extra, extra);
 
-
-        renderTargetContext->fillRectWithLocalMatrix(clip, newPaint, GrAA::kNo, SkMatrix::I(),
-                                                     proxyRect, inverse);
+        paint.addCoverageFragmentProcessor(std::move(fp));
+        renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo,
+                                                     SkMatrix::I(), proxyRect, inverse);
     }
 
     return true;
@@ -1543,7 +1539,7 @@
             paint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op);
         }
 
-        renderTargetContext->drawRect(GrNoClip(), paint, GrAA::kNo, SkMatrix::I(),
+        renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
                                       SkRect::Make(clipRect));
     }
 
diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp
index d9c1079..04b9b4f 100644
--- a/src/effects/SkDisplacementMapEffect.cpp
+++ b/src/effects/SkDisplacementMapEffect.cpp
@@ -366,7 +366,7 @@
         }
         paint.setGammaCorrect(renderTargetContext->isGammaCorrect());
 
-        renderTargetContext->drawRect(GrNoClip(), paint, GrAA::kNo, matrix,
+        renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix,
                                       SkRect::Make(colorBounds));
 
         offset->fX = bounds.left();
@@ -499,7 +499,7 @@
     : fDisplacementTransform(offsetMatrix, displacement, GrSamplerParams::kNone_FilterMode)
     , fDisplacementSampler(displacement)
     , fColorTransform(color, GrSamplerParams::kNone_FilterMode)
-    , fDomain(GrTextureDomain::MakeTexelDomain(color, SkIRect::MakeSize(colorDimensions)),
+    , fDomain(color, GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(colorDimensions)),
               GrTextureDomain::kDecal_Mode)
     , fColorSampler(color)
     , fColorSpaceXform(std::move(colorSpaceXform))
@@ -571,7 +571,7 @@
     const char* scaleUni = args.fUniformHandler->getUniformCStr(fScaleUni);
     const char* dColor = "dColor";
     const char* cCoords = "cCoords";
-    const char* nearZero = "1e-6"; // Since 6.10352e−5 is the smallest half float, use
+    const char* nearZero = "1e-6"; // Since 6.10352e-5 is the smallest half float, use
                                    // a number smaller than that to approximate 0, but
                                    // leave room for 32-bit float GPU rounding errors.
 
@@ -651,7 +651,7 @@
     pdman.set2f(fScaleUni, SkScalarToFloat(scaleX),
                 colorTex->origin() == kTopLeft_GrSurfaceOrigin ?
                 SkScalarToFloat(scaleY) : SkScalarToFloat(-scaleY));
-    fGLDomain.setData(pdman, displacementMap.domain(), colorTex->origin());
+    fGLDomain.setData(pdman, displacementMap.domain(), colorTex);
     if (SkToBool(displacementMap.colorSpaceXform())) {
         pdman.setSkMatrix44(fColorSpaceXformUni, displacementMap.colorSpaceXform()->srcToDst());
     }
diff --git a/src/effects/SkLightingImageFilter.cpp b/src/effects/SkLightingImageFilter.cpp
index 52c8947..5ea8c33 100644
--- a/src/effects/SkLightingImageFilter.cpp
+++ b/src/effects/SkLightingImageFilter.cpp
@@ -400,7 +400,8 @@
                                                               boundaryMode));
     paint.addColorFragmentProcessor(std::move(fp));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    renderTargetContext->fillRectToRect(clip, paint, GrAA::kNo, SkMatrix::I(), dstRect, srcRect);
+    renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
+                                        srcRect);
 }
 
 sk_sp<SkSpecialImage> SkLightingImageFilterInternal::filterImageGPU(
@@ -1690,20 +1691,16 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-namespace {
-
-GrTextureDomain create_domain(GrTexture* texture, const SkIRect* srcBounds,
-                              GrTextureDomain::Mode mode) {
+static GrTextureDomain create_domain(GrTexture* texture, const SkIRect* srcBounds,
+                                     GrTextureDomain::Mode mode) {
     if (srcBounds) {
-        SkRect texelDomain = GrTextureDomain::MakeTexelDomainForMode(texture, *srcBounds, mode);
-        return GrTextureDomain(texelDomain, mode);
+        SkRect texelDomain = GrTextureDomain::MakeTexelDomainForMode(*srcBounds, mode);
+        return GrTextureDomain(texture, texelDomain, mode);
     } else {
-        return GrTextureDomain(SkRect::MakeEmpty(), GrTextureDomain::kIgnore_Mode);
+        return GrTextureDomain::IgnoredDomain();
     }
 }
 
-};
-
 GrLightingEffect::GrLightingEffect(GrTexture* texture,
                                    const SkImageFilterLight* light,
                                    SkScalar surfaceScale,
@@ -1907,7 +1904,7 @@
     pdman.set1f(fSurfaceScaleUni, lighting.surfaceScale());
     sk_sp<SkImageFilterLight> transformedLight(
             lighting.light()->transform(lighting.filterMatrix()));
-    fDomain.setData(pdman, lighting.domain(), texture->origin());
+    fDomain.setData(pdman, lighting.domain(), texture);
     fLight->setData(pdman, transformedLight.get());
 }
 
diff --git a/src/effects/SkMatrixConvolutionImageFilter.cpp b/src/effects/SkMatrixConvolutionImageFilter.cpp
index fe6d8ac..6af8508 100644
--- a/src/effects/SkMatrixConvolutionImageFilter.cpp
+++ b/src/effects/SkMatrixConvolutionImageFilter.cpp
@@ -10,7 +10,6 @@
 #include "SkColorPriv.h"
 #include "SkReadBuffer.h"
 #include "SkSpecialImage.h"
-#include "SkSpecialSurface.h"
 #include "SkWriteBuffer.h"
 #include "SkRect.h"
 #include "SkUnPreMultiply.h"
@@ -282,7 +281,6 @@
     }
     return GrTextureDomain::kIgnore_Mode;
 }
-
 #endif
 
 sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialImage* source,
@@ -306,6 +304,12 @@
         fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE) {
         GrContext* context = source->getContext();
 
+        // Ensure the input is in the destination color space. Typically applyCropRect will have
+        // called pad_image to account for our dilation of bounds, so the result will already be
+        // moved to the destination color space. If a filter DAG avoids that, then we use this
+        // fall-back, which saves us from having to do the xform during the filter itself.
+        input = ImageToColorSpace(input.get(), ctx.outputProperties());
+
         sk_sp<GrTexture> inputTexture(input->asTextureRef(context));
         SkASSERT(inputTexture);
 
@@ -313,7 +317,6 @@
         offset->fY = bounds.top();
         bounds.offset(-inputOffset);
 
-        // SRGBTODO: handle sRGB here
         sk_sp<GrFragmentProcessor> fp(GrMatrixConvolutionEffect::Make(inputTexture.get(),
                                                                       bounds,
                                                                       fKernelSize,
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index 42986ce..6e2a805 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -13,7 +13,6 @@
 #include "SkReadBuffer.h"
 #include "SkRect.h"
 #include "SkSpecialImage.h"
-#include "SkSpecialSurface.h"
 #include "SkWriteBuffer.h"
 
 #if SK_SUPPORT_GPU
@@ -408,9 +407,8 @@
     paint.addColorFragmentProcessor(GrMorphologyEffect::Make(tex, direction, radius, morphType,
                                                              bounds));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    renderTargetContext->fillRectToRect(clip, paint, GrAA::kNo,
-                                        SkMatrix::I(), SkRect::Make(dstRect),
-                                        SkRect::Make(srcRect));
+    renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
+                                        SkRect::Make(dstRect), SkRect::Make(srcRect));
 }
 
 static void apply_morphology_rect_no_bounds(GrTextureProvider* provider,
@@ -430,7 +428,7 @@
     }
     paint.addColorFragmentProcessor(GrMorphologyEffect::Make(tex, direction, radius, morphType));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    renderTargetContext->fillRectToRect(clip, paint, GrAA::kNo, SkMatrix::I(),
+    renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
                                         SkRect::Make(dstRect), SkRect::Make(srcRect));
 }
 
@@ -493,9 +491,6 @@
     sk_sp<SkColorSpace> colorSpace = sk_ref_sp(outputProperties.colorSpace());
     GrPixelConfig config = GrRenderableConfigForColorSpace(colorSpace.get());
 
-    // We force the inputs to this filter into the destination color space in the calling code.
-    SkASSERT(input->getColorSpace() == colorSpace.get());
-
     // setup new clip
     const GrFixedClip clip(SkIRect::MakeWH(srcTexture->width(), srcTexture->height()));
 
@@ -546,23 +541,6 @@
                                                std::move(srcTexture), std::move(colorSpace),
                                                &input->props());
 }
-
-// Return a copy of 'src' transformed to the output's color space
-static sk_sp<SkSpecialImage> image_to_color_space(SkSpecialImage* src,
-                                                  const SkImageFilter::OutputProperties& outProps) {
-    sk_sp<SkSpecialSurface> surf(src->makeSurface(
-        outProps, SkISize::Make(src->width(), src->height())));
-    if (!surf) {
-        return sk_ref_sp(src);
-    }
-
-    SkCanvas* canvas = surf->getCanvas();
-    SkASSERT(canvas);
-
-    src->draw(canvas, 0, 0, nullptr);
-
-    return surf->makeImageSnapshot();
-}
 #endif
 
 sk_sp<SkSpecialImage> SkMorphologyImageFilter::onFilterImage(SkSpecialImage* source,
@@ -603,15 +581,11 @@
     if (source->isTextureBacked()) {
         GrContext* context = source->getContext();
 
-        // If the input is not yet already in the destination color space, do an explicit up-front
-        // conversion. This is extremely unlikely (maybe even impossible). Typically, applyCropRect
-        // will have called pad_image to account for our dilation of bounds, so the result will
-        // already be moved to the destination color space. If someone makes a filter DAG that
-        // avoids that, then we use this fall-back, which saves us from having to do the xform
-        // during the filter itself.
-        if (input->getColorSpace() != ctx.outputProperties().colorSpace()) {
-            input = image_to_color_space(input.get(), ctx.outputProperties());
-        }
+        // Ensure the input is in the destination color space. Typically applyCropRect will have
+        // called pad_image to account for our dilation of bounds, so the result will already be
+        // moved to the destination color space. If a filter DAG avoids that, then we use this
+        // fall-back, which saves us from having to do the xform during the filter itself.
+        input = ImageToColorSpace(input.get(), ctx.outputProperties());
 
         auto type = (kDilate_Op == this->op()) ? GrMorphologyEffect::kDilate_MorphologyType
                                                : GrMorphologyEffect::kErode_MorphologyType;
diff --git a/src/effects/SkShadowMaskFilter.cpp b/src/effects/SkShadowMaskFilter.cpp
index 38f2591..f1012f5 100755
--- a/src/effects/SkShadowMaskFilter.cpp
+++ b/src/effects/SkShadowMaskFilter.cpp
@@ -42,14 +42,14 @@
                           SkRect* maskRect) const override;
     bool directFilterMaskGPU(GrTextureProvider* texProvider,
                              GrRenderTargetContext* drawContext,
-                             GrPaint* grp,
+                             GrPaint&&,
                              const GrClip&,
                              const SkMatrix& viewMatrix,
                              const SkStrokeRec& strokeRec,
                              const SkPath& path) const override;
     bool directFilterRRectMaskGPU(GrContext*,
                                   GrRenderTargetContext* drawContext,
-                                  GrPaint* grp,
+                                  GrPaint&&,
                                   const GrClip&,
                                   const SkMatrix& viewMatrix,
                                   const SkStrokeRec& strokeRec,
@@ -166,7 +166,7 @@
 
 bool SkShadowMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider,
                                                  GrRenderTargetContext* drawContext,
-                                                 GrPaint* grp,
+                                                 GrPaint&& paint,
                                                  const GrClip& clip,
                                                  const SkMatrix& viewMatrix,
                                                  const SkStrokeRec& strokeRec,
@@ -179,12 +179,12 @@
     // have our own GeometryProc.
     if (path.isOval(nullptr) && path.getBounds().width() == path.getBounds().height()) {
         SkRRect rrect = SkRRect::MakeOval(path.getBounds());
-        return this->directFilterRRectMaskGPU(nullptr, drawContext, grp, clip, SkMatrix::I(),
-                                              strokeRec, rrect, rrect);
+        return this->directFilterRRectMaskGPU(nullptr, drawContext, std::move(paint), clip,
+                                              SkMatrix::I(), strokeRec, rrect, rrect);
     } else if (path.isRect(nullptr)) {
         SkRRect rrect = SkRRect::MakeRect(path.getBounds());
-        return this->directFilterRRectMaskGPU(nullptr, drawContext, grp, clip, SkMatrix::I(),
-                                              strokeRec, rrect, rrect);
+        return this->directFilterRRectMaskGPU(nullptr, drawContext, std::move(paint), clip,
+                                              SkMatrix::I(), strokeRec, rrect, rrect);
     }
 
     // TODO
@@ -193,7 +193,7 @@
 
 bool SkShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
                                                       GrRenderTargetContext* renderTargetContext,
-                                                      GrPaint* grp,
+                                                      GrPaint&& paint,
                                                       const GrClip& clip,
                                                       const SkMatrix& viewMatrix,
                                                       const SkStrokeRec& strokeRec,
@@ -252,14 +252,14 @@
 
         const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
 
-        GrPaint newPaint(*grp);
+        GrPaint newPaint(paint);
         GrColor4f color = newPaint.getColor4f();
         color.fRGBA[3] *= fAmbientAlpha;
         newPaint.setColor4f(color);
         SkStrokeRec ambientStrokeRec(SkStrokeRec::kHairline_InitStyle);
         ambientStrokeRec.setStrokeStyle(srcSpaceAmbientRadius, false);
 
-        renderTargetContext->drawShadowRRect(clip, newPaint, viewMatrix, ambientRRect,
+        renderTargetContext->drawShadowRRect(clip, std::move(newPaint), viewMatrix, ambientRRect,
                                              devSpaceAmbientRadius,
                                              GrStyle(ambientStrokeRec, nullptr));
     }
@@ -310,11 +310,10 @@
         SkScalar filledArea = (spotShadowRRect.height() + srcSpaceSpotRadius) *
                               (spotShadowRRect.width() + srcSpaceSpotRadius);
 
-        GrPaint newPaint(*grp);
-        GrColor4f color = newPaint.getColor4f();
+        GrColor4f color = paint.getColor4f();
         color.fRGBA[3] *= fSpotAlpha;
-        newPaint.setColor4f(color);
-        
+        paint.setColor4f(color);
+
         SkStrokeRec spotStrokeRec(SkStrokeRec::kFill_InitStyle);
         // If the area of the stroked geometry is larger than the fill geometry,
         // or if the caster is transparent, just fill it.
@@ -337,9 +336,8 @@
 
         spotShadowRRect.offset(spotOffset.fX, spotOffset.fY);
 
-        renderTargetContext->drawShadowRRect(clip, newPaint, viewMatrix, spotShadowRRect,
-                                             devSpaceSpotRadius,
-                                             GrStyle(spotStrokeRec, nullptr));
+        renderTargetContext->drawShadowRRect(clip, std::move(paint), viewMatrix, spotShadowRRect,
+                                             devSpaceSpotRadius, GrStyle(spotStrokeRec, nullptr));
     }
 
     return true;
diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp
index d90cb91..7038db2 100644
--- a/src/effects/SkXfermodeImageFilter.cpp
+++ b/src/effects/SkXfermodeImageFilter.cpp
@@ -6,8 +6,8 @@
  */
 
 #include "SkXfermodeImageFilter.h"
+#include "SkArithmeticImageFilter.h"
 #include "SkArithmeticModePriv.h"
-
 #include "SkCanvas.h"
 #include "SkColorPriv.h"
 #include "SkReadBuffer.h"
@@ -22,7 +22,6 @@
 #include "effects/GrConstColorProcessor.h"
 #include "effects/GrTextureDomain.h"
 #include "effects/GrSimpleTextureEffect.h"
-#include "SkArithmeticMode_gpu.h"
 #include "SkGr.h"
 #include "SkGrPriv.h"
 #endif
@@ -52,12 +51,14 @@
 
     void flatten(SkWriteBuffer&) const override;
 
-    virtual void drawForeground(SkCanvas* canvas, SkSpecialImage*, const SkIRect&) const;
+    void drawForeground(SkCanvas* canvas, SkSpecialImage*, const SkIRect&) const;
 #if SK_SUPPORT_GPU
-    virtual sk_sp<GrFragmentProcessor> makeFGFrag(sk_sp<GrFragmentProcessor> bgFP) const;
+    sk_sp<GrFragmentProcessor> makeFGFrag(sk_sp<GrFragmentProcessor> bgFP) const;
 #endif
 
 private:
+    static sk_sp<SkFlattenable> LegacyArithmeticCreateProc(SkReadBuffer& buffer);
+
     SkBlendMode fMode;
 
     friend class SkXfermodeImageFilter;
@@ -108,10 +109,9 @@
         return SkXfermodeImageFilter::Make((SkBlendMode)mode, common.getInput(0),
                                            common.getInput(1), &common.cropRect());
     } else {
-        return SkXfermodeImageFilter::MakeArithmetic(arith.fK[0], arith.fK[1], arith.fK[2],
-                                                     arith.fK[3], arith.fEnforcePMColor,
-                                                     common.getInput(0),
-                                                     common.getInput(1), &common.cropRect());
+        return SkArithmeticImageFilter::Make(arith.fK[0], arith.fK[1], arith.fK[2], arith.fK[3],
+                                             arith.fEnforcePMColor, common.getInput(0),
+                                             common.getInput(1), &common.cropRect());
     }
 }
 
@@ -257,8 +257,7 @@
                                                                    outputProperties.colorSpace());
         bgFP = GrTextureDomainEffect::Make(
                             backgroundTex.get(), std::move(bgXform), backgroundMatrix,
-                            GrTextureDomain::MakeTexelDomain(backgroundTex.get(),
-                                                             background->subset()),
+                            GrTextureDomain::MakeTexelDomain(background->subset()),
                             GrTextureDomain::kDecal_Mode,
                             GrSamplerParams::kNone_FilterMode);
     } else {
@@ -277,8 +276,7 @@
 
         foregroundFP = GrTextureDomainEffect::Make(
                             foregroundTex.get(), std::move(fgXform), foregroundMatrix,
-                            GrTextureDomain::MakeTexelDomain(foregroundTex.get(),
-                                                             foreground->subset()),
+                            GrTextureDomain::MakeTexelDomain(foreground->subset()),
                             GrTextureDomain::kDecal_Mode,
                             GrSamplerParams::kNone_FilterMode);
 
@@ -307,7 +305,8 @@
 
     SkMatrix matrix;
     matrix.setTranslate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top()));
-    renderTargetContext->drawRect(GrNoClip(), paint, GrAA::kNo, matrix, SkRect::Make(bounds));
+    renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix,
+                                  SkRect::Make(bounds));
 
     return SkSpecialImage::MakeDeferredFromGpu(context,
                                                SkIRect::MakeWH(bounds.width(), bounds.height()),
@@ -337,196 +336,23 @@
 }
 
 #endif
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-class SkArithmeticImageFilter : public SkXfermodeImageFilter_Base {
-public:
-    SkArithmeticImageFilter(float k1, float k2, float k3, float k4, bool enforcePMColor,
-                            sk_sp<SkImageFilter> inputs[2], const CropRect* cropRect)
-        // need to pass a blendmode to our inherited constructor, but we ignore it
-        : SkXfermodeImageFilter_Base(SkBlendMode::kSrcOver, inputs, cropRect)
-        , fK{ k1, k2, k3, k4 }
-        , fEnforcePMColor(enforcePMColor)
-    {}
-
-    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkArithmeticImageFilter)
-
-protected:
-    void flatten(SkWriteBuffer& buffer) const override {
-        this->INHERITED::flatten(buffer);
-        for (int i = 0; i < 4; ++i) {
-            buffer.writeScalar(fK[i]);
-        }
-        buffer.writeBool(fEnforcePMColor);
-    }
-    void drawForeground(SkCanvas* canvas, SkSpecialImage*, const SkIRect&) const override;
-#if SK_SUPPORT_GPU
-    sk_sp<GrFragmentProcessor> makeFGFrag(sk_sp<GrFragmentProcessor> bgFP) const override {
-        return GrArithmeticFP::Make(fK[0], fK[1], fK[2], fK[3], fEnforcePMColor, std::move(bgFP));
-    }
-#endif
-
-private:
-    const float fK[4];
-    const bool fEnforcePMColor;
-
-    friend class SkXfermodeImageFilter;
-
-    typedef SkXfermodeImageFilter_Base INHERITED;
-};
-
-sk_sp<SkFlattenable> SkArithmeticImageFilter::CreateProc(SkReadBuffer& buffer) {
+sk_sp<SkFlattenable> SkXfermodeImageFilter_Base::LegacyArithmeticCreateProc(SkReadBuffer& buffer) {
     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2);
-
-    // skip the mode (srcover) our parent-class wrote
+    // skip the unused mode (srcover) field
     SkDEBUGCODE(int mode =) unflatten_blendmode(buffer, nullptr);
     if (!buffer.isValid()) {
         return nullptr;
     }
     SkASSERT(SkBlendMode::kSrcOver == (SkBlendMode)mode);
-
     float k[4];
     for (int i = 0; i < 4; ++i) {
         k[i] = buffer.readScalar();
     }
     const bool enforcePMColor = buffer.readBool();
-    return SkXfermodeImageFilter::MakeArithmetic(k[0], k[1], k[2], k[3], enforcePMColor,
-                                                 common.getInput(0), common.getInput(1),
-                                                 &common.cropRect());
-}
-
-#include "SkNx.h"
-
-static Sk4f pin(float min, const Sk4f& val, float max) {
-    return Sk4f::Max(min, Sk4f::Min(val, max));
-}
-
-template<bool EnforcePMColor> void arith_span(const float k[], SkPMColor dst[],
-                                              const SkPMColor src[], int count) {
-    const Sk4f k1 = k[0] * (1/255.0f),
-               k2 = k[1],
-               k3 = k[2],
-               k4 = k[3] * 255.0f + 0.5f;
-
-    for (int i = 0; i < count; i++) {
-        Sk4f s = SkNx_cast<float>(Sk4b::Load(src+i)),
-        d = SkNx_cast<float>(Sk4b::Load(dst+i)),
-        r = pin(0, k1*s*d + k2*s + k3*d + k4, 255);
-        if (EnforcePMColor) {
-            Sk4f a = SkNx_shuffle<3,3,3,3>(r);
-            r = Sk4f::Min(a, r);
-        }
-        SkNx_cast<uint8_t>(r).store(dst+i);
-    }
-}
-
-// apply mode to src==transparent (0)
-template<bool EnforcePMColor> void arith_transparent(const float k[], SkPMColor dst[], int count) {
-    const Sk4f k3 = k[2],
-               k4 = k[3] * 255.0f + 0.5f;
-
-    for (int i = 0; i < count; i++) {
-        Sk4f d = SkNx_cast<float>(Sk4b::Load(dst+i)),
-             r = pin(0, k3*d + k4, 255);
-        if (EnforcePMColor) {
-            Sk4f a = SkNx_shuffle<3,3,3,3>(r);
-            r = Sk4f::Min(a, r);
-        }
-        SkNx_cast<uint8_t>(r).store(dst+i);
-    }
-}
-
-static bool intersect(SkPixmap* dst, SkPixmap* src, int srcDx, int srcDy) {
-    SkIRect dstR = SkIRect::MakeWH(dst->width(), dst->height());
-    SkIRect srcR = SkIRect::MakeXYWH(srcDx, srcDy, src->width(), src->height());
-    SkIRect sect;
-    if (!sect.intersect(dstR, srcR)) {
-        return false;
-    }
-    *dst = SkPixmap(dst->info().makeWH(sect.width(), sect.height()),
-                    dst->addr(sect.fLeft, sect.fTop),
-                    dst->rowBytes());
-    *src = SkPixmap(src->info().makeWH(sect.width(), sect.height()),
-                    src->addr(SkTMax(0, -srcDx), SkTMax(0, -srcDy)),
-                    src->rowBytes());
-    return true;
-}
-
-void SkArithmeticImageFilter::drawForeground(SkCanvas* canvas, SkSpecialImage* img,
-                                             const SkIRect& fgBounds) const {
-    SkPixmap dst;
-    if (!canvas->peekPixels(&dst)) {
-        return;
-    }
-
-    const SkMatrix& ctm = canvas->getTotalMatrix();
-    SkASSERT(ctm.getType() <= SkMatrix::kTranslate_Mask);
-    const int dx = SkScalarRoundToInt(ctm.getTranslateX());
-    const int dy = SkScalarRoundToInt(ctm.getTranslateY());
-
-    if (img) {
-        SkBitmap srcBM;
-        SkPixmap src;
-        if (!img->getROPixels(&srcBM)) {
-            return;
-        }
-        srcBM.lockPixels();
-        if (!srcBM.peekPixels(&src)) {
-            return;
-        }
-
-        auto proc = fEnforcePMColor ? arith_span<true> : arith_span<false>;
-        SkPixmap tmpDst = dst;
-        if (intersect(&tmpDst, &src, fgBounds.fLeft + dx, fgBounds.fTop + dy)) {
-            for (int y = 0; y < tmpDst.height(); ++y) {
-                proc(fK, tmpDst.writable_addr32(0, y), src.addr32(0, y), tmpDst.width());
-            }
-        }
-    }
-
-    // Now apply the mode with transparent-color to the outside of the fg image
-    SkRegion outside(SkIRect::MakeWH(dst.width(), dst.height()));
-    outside.op(fgBounds.makeOffset(dx, dy), SkRegion::kDifference_Op);
-    auto proc = fEnforcePMColor ? arith_transparent<true> : arith_transparent<false>;
-    for (SkRegion::Iterator iter(outside); !iter.done(); iter.next()) {
-        const SkIRect r = iter.rect();
-        for (int y = r.fTop; y < r.fBottom; ++y) {
-            proc(fK, dst.writable_addr32(r.fLeft, y), r.width());
-        }
-    }
-}
-
-sk_sp<SkImageFilter> SkXfermodeImageFilter::MakeArithmetic(float k1, float k2, float k3, float k4,
-                                                           bool enforcePMColor,
-                                                           sk_sp<SkImageFilter> background,
-                                                           sk_sp<SkImageFilter> foreground,
-                                                           const SkImageFilter::CropRect* crop) {
-    if (!SkScalarIsFinite(k1) || !SkScalarIsFinite(k2) ||
-        !SkScalarIsFinite(k3) || !SkScalarIsFinite(k4)) {
-        return nullptr;
-    }
-
-    // are we nearly some other "std" mode?
-    int mode = -1;  // illegal mode
-    if (SkScalarNearlyZero(k1) && SkScalarNearlyEqual(k2, SK_Scalar1) &&
-        SkScalarNearlyZero(k3) && SkScalarNearlyZero(k4)) {
-        mode = (int)SkBlendMode::kSrc;
-    } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) &&
-               SkScalarNearlyEqual(k3, SK_Scalar1) && SkScalarNearlyZero(k4)) {
-        mode = (int)SkBlendMode::kDst;
-    } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) &&
-               SkScalarNearlyZero(k3) && SkScalarNearlyZero(k4)) {
-        mode = (int)SkBlendMode::kClear;
-    }
-    if (mode >= 0) {
-        return SkXfermodeImageFilter::Make((SkBlendMode)mode,
-                                           std::move(background), std::move(foreground), crop);
-    }
-
-    sk_sp<SkImageFilter> inputs[2] = { std::move(background), std::move(foreground) };
-    return sk_sp<SkImageFilter>(new SkArithmeticImageFilter(k1, k2, k3, k4, enforcePMColor,
-                                                            inputs, crop));
+    return SkArithmeticImageFilter::Make(k[0], k[1], k[2], k[3], enforcePMColor, common.getInput(0),
+                                         common.getInput(1), &common.cropRect());
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -536,5 +362,9 @@
     // manually register the legacy serialized name "SkXfermodeImageFilter"
     SkFlattenable::Register("SkXfermodeImageFilter", SkXfermodeImageFilter_Base::CreateProc,
                             SkFlattenable::kSkImageFilter_Type);
-    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkArithmeticImageFilter)
+    // manually register the legacy serialized name "SkArithmeticImageFilter" from when that filter
+    // was implemented as a xfermode image filter.
+    SkFlattenable::Register("SkArithmeticImageFilter",
+                            SkXfermodeImageFilter_Base::LegacyArithmeticCreateProc,
+                            SkFlattenable::kSkImageFilter_Type);
 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/src/effects/gradients/SkGradientShaderPriv.h b/src/effects/gradients/SkGradientShaderPriv.h
index 4b7bbb0..48ccbd5 100644
--- a/src/effects/gradients/SkGradientShaderPriv.h
+++ b/src/effects/gradients/SkGradientShaderPriv.h
@@ -10,15 +10,17 @@
 
 #include "SkGradientBitmapCache.h"
 #include "SkGradientShader.h"
+
+#include "SkAutoMalloc.h"
 #include "SkClampRange.h"
 #include "SkColorPriv.h"
 #include "SkColorSpace.h"
-#include "SkReadBuffer.h"
-#include "SkWriteBuffer.h"
 #include "SkMallocPixelRef.h"
-#include "SkUtils.h"
-#include "SkShader.h"
 #include "SkOnce.h"
+#include "SkReadBuffer.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "SkWriteBuffer.h"
 
 #if SK_SUPPORT_GPU
     #define GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS 1
diff --git a/src/fonts/SkTestScalerContext.cpp b/src/fonts/SkTestScalerContext.cpp
index d84571d..fce249f 100644
--- a/src/fonts/SkTestScalerContext.cpp
+++ b/src/fonts/SkTestScalerContext.cpp
@@ -171,8 +171,8 @@
 }
 
 int SkTestTypeface::onCharsToGlyphs(const void* chars, Encoding encoding,
-                            uint16_t glyphs[], int glyphCount) const {
-    SkASSERT(encoding == kUTF16_Encoding);
+                                    uint16_t glyphs[], int glyphCount) const {
+    SkASSERT(encoding == kUTF32_Encoding);
     for (int index = 0; index < glyphCount; ++index) {
         SkUnichar ch = ((SkUnichar*) chars)[index];
         glyphs[index] = fTestFont->codeToIndex(ch);
@@ -214,7 +214,7 @@
     uint16_t generateCharToGlyph(SkUnichar uni) override {
         uint16_t glyph;
         (void) this->getTestTypeface()->onCharsToGlyphs((const void *) &uni,
-                                                        SkTypeface::kUTF16_Encoding, &glyph, 1);
+                                                        SkTypeface::kUTF32_Encoding, &glyph, 1);
         return glyph;
     }
 
diff --git a/src/gpu/GrAllocator.h b/src/gpu/GrAllocator.h
index 5b9bd5b..e5d2f4e 100644
--- a/src/gpu/GrAllocator.h
+++ b/src/gpu/GrAllocator.h
@@ -258,6 +258,13 @@
         return *(T*)item;
     }
 
+    template <typename... Args> T& emplace_back(Args&&... args) {
+        void* item = fAllocator.push_back();
+        SkASSERT(item);
+        new (item) T(std::forward<Args>(args)...);
+        return *(T*)item;
+    }
+
     /**
      * Remove the last item, only call if count() != 0
      */
diff --git a/src/gpu/GrAuditTrail.cpp b/src/gpu/GrAuditTrail.cpp
index 203af35..133ffea 100644
--- a/src/gpu/GrAuditTrail.cpp
+++ b/src/gpu/GrAuditTrail.cpp
@@ -10,7 +10,7 @@
 
 const int GrAuditTrail::kGrAuditTrailInvalidID = -1;
 
-void GrAuditTrail::addOp(const GrOp* op) {
+void GrAuditTrail::addOp(const GrOp* op, GrGpuResource::UniqueID renderTargetID) {
     SkASSERT(fEnabled);
     Op* auditOp = new Op;
     fOpPool.emplace_back(auditOp);
@@ -44,7 +44,7 @@
 
     // We use the op pointer as a key to find the OpNode we are 'glomming' ops onto
     fIDLookup.set(op->uniqueID(), auditOp->fOpListID);
-    OpNode* opNode = new OpNode(op->renderTargetUniqueID());
+    OpNode* opNode = new OpNode(renderTargetID);
     opNode->fBounds = op->bounds();
     opNode->fChildren.push_back(auditOp);
     fOpList.emplace_back(opNode);
@@ -226,7 +226,7 @@
 SkString GrAuditTrail::toJson(bool prettyPrint) const {
     SkString json;
     json.append("{");
-    JsonifyTArray(&json, "Batches", fOpList, false);
+    JsonifyTArray(&json, "Ops", fOpList, false);
     json.append("}");
 
     if (prettyPrint) {
@@ -241,7 +241,7 @@
     json.append("{");
     Ops** ops = fClientIDLookup.find(clientID);
     if (ops) {
-        JsonifyTArray(&json, "Batches", **ops, false);
+        JsonifyTArray(&json, "Ops", **ops, false);
     }
     json.appendf("}");
 
@@ -266,7 +266,7 @@
     json.append("{");
     json.appendf("\"Name\": \"%s\",", fName.c_str());
     json.appendf("\"ClientID\": \"%d\",", fClientID);
-    json.appendf("\"BatchListID\": \"%d\",", fOpListID);
+    json.appendf("\"OpListID\": \"%d\",", fOpListID);
     json.appendf("\"ChildID\": \"%d\",", fChildID);
     skrect_to_json(&json, "Bounds", fBounds);
     if (fStackTrace.count()) {
@@ -288,7 +288,7 @@
     json.append("{");
     json.appendf("\"RenderTarget\": \"%u\",", fRenderTargetUniqueID.asUInt());
     skrect_to_json(&json, "Bounds", fBounds);
-    JsonifyTArray(&json, "Batches", fChildren, true);
+    JsonifyTArray(&json, "Ops", fChildren, true);
     json.append("}");
     return json;
 }
diff --git a/src/gpu/GrAutoLocaleSetter.h b/src/gpu/GrAutoLocaleSetter.h
index 564abd9..cec041e 100644
--- a/src/gpu/GrAutoLocaleSetter.h
+++ b/src/gpu/GrAutoLocaleSetter.h
@@ -20,6 +20,9 @@
 
 #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
 #include <xlocale.h>
+#define HAVE_XLOCALE 1
+#else
+#define HAVE_XLOCALE 0
 #endif
 
 #if defined(SK_BUILD_FOR_ANDROID) || defined(__UCLIBC__) || defined(_NEWLIB_VERSION)
@@ -45,6 +48,12 @@
             fShouldRestoreLocale = false;
         }
 #elif HAVE_LOCALE_T
+#if HAVE_XLOCALE
+        // In xlocale nullptr means the C locale.
+        if (0 == strcmp(name, "C")) {
+            name = nullptr;
+        }
+#endif
         fLocale = newlocale(LC_ALL, name, 0);
         if (fLocale) {
             fOldLocale = uselocale(fLocale);
@@ -82,5 +91,6 @@
 };
 
 #undef HAVE_LOCALE_T
+#undef HAVE_XLOCALE
 
 #endif
diff --git a/src/gpu/GrBlurUtils.cpp b/src/gpu/GrBlurUtils.cpp
index 2a3c0a8..23a3c6c 100644
--- a/src/gpu/GrBlurUtils.cpp
+++ b/src/gpu/GrBlurUtils.cpp
@@ -34,27 +34,26 @@
                       const GrClip& clip,
                       const SkMatrix& viewMatrix,
                       const SkIRect& maskRect,
-                      GrPaint* grp,
+                      GrPaint&& paint,
                       sk_sp<GrTextureProxy> mask) {
-
     // TODO: defer this instantiation
     GrTexture* maskTex = mask->instantiate(textureProvider);
     if (!maskTex) {
         return false;
     }
+    SkMatrix inverse;
+    if (!viewMatrix.invert(&inverse)) {
+        return false;
+    }
 
     SkMatrix matrix;
     matrix.setTranslate(-SkIntToScalar(maskRect.fLeft), -SkIntToScalar(maskRect.fTop));
     // TODO: this divide relies on the instantiated texture's size!
     matrix.postIDiv(maskTex->width(), maskTex->height());
     matrix.preConcat(viewMatrix);
-    grp->addCoverageFragmentProcessor(GrSimpleTextureEffect::Make(maskTex, nullptr, matrix));
+    paint.addCoverageFragmentProcessor(GrSimpleTextureEffect::Make(maskTex, nullptr, matrix));
 
-    SkMatrix inverse;
-    if (!viewMatrix.invert(&inverse)) {
-        return false;
-    }
-    renderTargetContext->fillRectWithLocalMatrix(clip, *grp, GrAA::kNo, SkMatrix::I(),
+    renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(),
                                                  SkRect::Make(maskRect), inverse);
     return true;
 }
@@ -66,7 +65,7 @@
                                      const SkPath& devPath,
                                      const SkMaskFilter* filter,
                                      const SkIRect& clipBounds,
-                                     GrPaint* grp,
+                                     GrPaint&& paint,
                                      SkStrokeRec::InitStyle fillOrHairline) {
     SkMask  srcM, dstM;
     if (!SkDraw::DrawToMask(devPath, &clipBounds, filter, &viewMatrix, &srcM,
@@ -109,8 +108,8 @@
     texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig,
                          dstM.fImage, dstM.fRowBytes);
 
-    return draw_mask(renderTargetContext, context->textureProvider(),
-                     clipData, viewMatrix, dstM.fBounds, grp, sk_ref_sp(proxy->asTextureProxy()));
+    return draw_mask(renderTargetContext, context->textureProvider(), clipData, viewMatrix,
+                     dstM.fBounds, std::move(paint), sk_ref_sp(proxy->asTextureProxy()));
 }
 
 // Create a mask of 'devPath' and place the result in 'mask'.
@@ -134,25 +133,26 @@
 
     rtContext->priv().absClear(nullptr, 0x0);
 
-    GrPaint tempPaint;
-    tempPaint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op);
+    GrPaint maskPaint;
+    maskPaint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op);
 
     // setup new clip
     const SkIRect clipRect = SkIRect::MakeWH(maskRect.width(), maskRect.height());
     GrFixedClip clip(clipRect);
 
     // Draw the mask into maskTexture with the path's integerized top-left at
-    // the origin using tempPaint.
+    // the origin using maskPaint.
     SkMatrix translate;
     translate.setTranslate(-SkIntToScalar(maskRect.fLeft), -SkIntToScalar(maskRect.fTop));
-    rtContext->drawPath(clip, tempPaint, aa, translate, devPath, GrStyle(fillOrHairline));
+    rtContext->drawPath(clip, std::move(maskPaint), aa, translate, devPath,
+                        GrStyle(fillOrHairline));
     return sk_ref_sp(rtContext->asDeferredTexture());
 }
 
 static void draw_path_with_mask_filter(GrContext* context,
                                        GrRenderTargetContext* renderTargetContext,
                                        const GrClip& clip,
-                                       GrPaint* paint,
+                                       GrPaint&& paint,
                                        GrAA aa,
                                        const SkMatrix& viewMatrix,
                                        const SkMaskFilter* maskFilter,
@@ -217,7 +217,7 @@
 
         if (maskFilter->directFilterMaskGPU(context->textureProvider(),
                                             renderTargetContext,
-                                            paint,
+                                            std::move(paint),
                                             clip,
                                             viewMatrix,
                                             SkStrokeRec(fillOrHairline),
@@ -239,8 +239,8 @@
                                                                        viewMatrix,
                                                                        finalIRect);
             if (filtered) {
-                if (draw_mask(renderTargetContext, context->textureProvider(),
-                              clip, viewMatrix, finalIRect, paint, std::move(filtered))) {
+                if (draw_mask(renderTargetContext, context->textureProvider(), clip, viewMatrix,
+                              finalIRect, std::move(paint), std::move(filtered))) {
                     // This path is completely drawn
                     return;
                 }
@@ -248,22 +248,22 @@
         }
     }
 
-    sw_draw_with_mask_filter(context, renderTargetContext, clip, viewMatrix, *path,
-                             maskFilter, clipBounds, paint, fillOrHairline);
+    sw_draw_with_mask_filter(context, renderTargetContext, clip, viewMatrix, *path, maskFilter,
+                             clipBounds, std::move(paint), fillOrHairline);
 }
 
 void GrBlurUtils::drawPathWithMaskFilter(GrContext* context,
                                          GrRenderTargetContext* renderTargetContext,
                                          const GrClip& clip,
                                          const SkPath& path,
-                                         GrPaint* paint,
+                                         GrPaint&& paint,
                                          GrAA aa,
                                          const SkMatrix& viewMatrix,
                                          const SkMaskFilter* mf,
                                          const GrStyle& style,
                                          bool pathIsMutable) {
-    draw_path_with_mask_filter(context, renderTargetContext, clip, paint, aa, viewMatrix, mf,
-                               style, &path, pathIsMutable);
+    draw_path_with_mask_filter(context, renderTargetContext, clip, std::move(paint), aa, viewMatrix,
+                               mf, style, &path, pathIsMutable);
 }
 
 void GrBlurUtils::drawPathWithMaskFilter(GrContext* context,
@@ -310,10 +310,9 @@
     SkMaskFilter* mf = paint.getMaskFilter();
     if (mf && !mf->asFragmentProcessor(nullptr, nullptr, viewMatrix)) {
         // The MaskFilter wasn't already handled in SkPaintToGrPaint
-        draw_path_with_mask_filter(context, renderTargetContext, clip, &grPaint, aa, viewMatrix,
-                                   mf, style,
-                                   path, pathIsMutable);
+        draw_path_with_mask_filter(context, renderTargetContext, clip, std::move(grPaint), aa,
+                                   viewMatrix, mf, style, path, pathIsMutable);
     } else {
-        renderTargetContext->drawPath(clip, grPaint, aa, viewMatrix, *path, style);
+        renderTargetContext->drawPath(clip, std::move(grPaint), aa, viewMatrix, *path, style);
     }
 }
diff --git a/src/gpu/GrBlurUtils.h b/src/gpu/GrBlurUtils.h
index be0d2fb..bf6d638 100644
--- a/src/gpu/GrBlurUtils.h
+++ b/src/gpu/GrBlurUtils.h
@@ -49,13 +49,12 @@
                                 GrRenderTargetContext*,
                                 const GrClip&,
                                 const SkPath& path,
-                                GrPaint*,
+                                GrPaint&&,
                                 GrAA,
                                 const SkMatrix& viewMatrix,
                                 const SkMaskFilter*,
                                 const GrStyle&,
                                 bool pathIsMutable);
-
 };
 
 #endif
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index 0cc3346..92c22b5 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -170,7 +170,7 @@
     static const char* kConfigNames[] = {
         "Unknown",       // kUnknown_GrPixelConfig
         "Alpha8",        // kAlpha_8_GrPixelConfig,
-        "Index8",        // kIndex_8_GrPixelConfig,
+        "Gray8",         // kGray_8_GrPixelConfig,
         "RGB565",        // kRGB_565_GrPixelConfig,
         "RGBA444",       // kRGBA_4444_GrPixelConfig,
         "RGBA8888",      // kRGBA_8888_GrPixelConfig,
@@ -188,7 +188,7 @@
     };
     GR_STATIC_ASSERT(0  == kUnknown_GrPixelConfig);
     GR_STATIC_ASSERT(1  == kAlpha_8_GrPixelConfig);
-    GR_STATIC_ASSERT(2  == kIndex_8_GrPixelConfig);
+    GR_STATIC_ASSERT(2  == kGray_8_GrPixelConfig);
     GR_STATIC_ASSERT(3  == kRGB_565_GrPixelConfig);
     GR_STATIC_ASSERT(4  == kRGBA_4444_GrPixelConfig);
     GR_STATIC_ASSERT(5  == kRGBA_8888_GrPixelConfig);
diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp
index a7bcce4..7b50d52 100644
--- a/src/gpu/GrClipStackClip.cpp
+++ b/src/gpu/GrClipStackClip.cpp
@@ -27,6 +27,7 @@
 typedef GrReducedClip::ElementList ElementList;
 
 static const int kMaxAnalyticElements = 4;
+const char GrClipStackClip::kMaskTestTag[] = "clip_mask";
 
 bool GrClipStackClip::quickContains(const SkRect& rect) const {
     if (!fStack || fStack->isWideOpen()) {
@@ -341,9 +342,9 @@
         if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) {
             // The clip geometry is complex enough that it will be more efficient to create it
             // entirely in software
-            result = CreateSoftwareClipMask(context, reducedClip);
+            result = this->createSoftwareClipMask(context, reducedClip);
         } else {
-            result = CreateAlphaClipMask(context, reducedClip);
+            result = this->createAlphaClipMask(context, reducedClip);
         }
 
         if (result) {
@@ -384,9 +385,9 @@
 ////////////////////////////////////////////////////////////////////////////////
 // Create a 8-bit clip mask in alpha
 
-static void GetClipMaskKey(int32_t clipGenID, const SkIRect& bounds, GrUniqueKey* key) {
+static void create_clip_mask_key(int32_t clipGenID, const SkIRect& bounds, GrUniqueKey* key) {
     static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
-    GrUniqueKey::Builder builder(key, kDomain, 3);
+    GrUniqueKey::Builder builder(key, kDomain, 3, GrClipStackClip::kMaskTestTag);
     builder[0] = clipGenID;
     // SkToS16 because image filters outset layers to a size indicated by the filter, which can
     // sometimes result in negative coordinates from clip space.
@@ -394,11 +395,25 @@
     builder[2] = SkToS16(bounds.fTop) | (SkToS16(bounds.fBottom) << 16);
 }
 
-sk_sp<GrTexture> GrClipStackClip::CreateAlphaClipMask(GrContext* context,
-                                                      const GrReducedClip& reducedClip) {
+static void add_invalidate_on_pop_message(const SkClipStack& stack, int32_t clipGenID,
+                                          const GrUniqueKey& clipMaskKey) {
+    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));
+            element->addResourceInvalidationMessage(std::move(msg));
+            return;
+        }
+    }
+    SkDEBUGFAIL("Gen ID was not found in stack.");
+}
+
+sk_sp<GrTexture> GrClipStackClip::createAlphaClipMask(GrContext* context,
+                                                      const GrReducedClip& reducedClip) const {
     GrResourceProvider* resourceProvider = context->resourceProvider();
     GrUniqueKey key;
-    GetClipMaskKey(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
+    create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
     if (GrTexture* texture = resourceProvider->findAndRefTextureByUniqueKey(key)) {
         return sk_sp<GrTexture>(texture);
     }
@@ -423,13 +438,14 @@
     }
 
     texture->resourcePriv().setUniqueKey(key);
+    add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
     return texture;
 }
 
-sk_sp<GrTexture> GrClipStackClip::CreateSoftwareClipMask(GrContext* context,
-                                                         const GrReducedClip& reducedClip) {
+sk_sp<GrTexture> GrClipStackClip::createSoftwareClipMask(GrContext* context,
+                                                         const GrReducedClip& reducedClip) const {
     GrUniqueKey key;
-    GetClipMaskKey(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
+    create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key);
     if (GrTexture* texture = context->textureProvider()->findAndRefTextureByUniqueKey(key)) {
         return sk_sp<GrTexture>(texture);
     }
@@ -493,6 +509,6 @@
     }
 
     tex->resourcePriv().setUniqueKey(key);
-
+    add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key);
     return sk_ref_sp(tex);
 }
diff --git a/src/gpu/GrClipStackClip.h b/src/gpu/GrClipStackClip.h
index f5b8411..1c4d40b 100644
--- a/src/gpu/GrClipStackClip.h
+++ b/src/gpu/GrClipStackClip.h
@@ -40,6 +40,9 @@
 
     bool isRRect(const SkRect& rtBounds, SkRRect* rr, GrAA* aa) const override;
 
+    sk_sp<GrTexture> testingOnly_createClipMask(GrContext*) const;
+    static const char kMaskTestTag[];
+
 private:
     static bool PathNeedsSWRenderer(GrContext* context,
                                     bool hasUserStencilSettings,
@@ -51,18 +54,15 @@
 
     // Creates an alpha mask of the clip. The mask is a rasterization of elements through the
     // rect specified by clipSpaceIBounds.
-    static sk_sp<GrTexture> CreateAlphaClipMask(GrContext*, const GrReducedClip&);
+    sk_sp<GrTexture> createAlphaClipMask(GrContext*, const GrReducedClip&) const;
 
     // Similar to createAlphaClipMask but it rasterizes in SW and uploads to the result texture.
-    static sk_sp<GrTexture> CreateSoftwareClipMask(GrContext*, const GrReducedClip&);
+    sk_sp<GrTexture> createSoftwareClipMask(GrContext*, const GrReducedClip&) const;
 
-   static bool UseSWOnlyPath(GrContext*,
-                             bool hasUserStencilSettings,
-                             const GrRenderTargetContext*,
-                             const GrReducedClip&);
-
-    static GrTexture* CreateCachedMask(int width, int height, const GrUniqueKey& key,
-                                       bool renderTarget);
+    static bool UseSWOnlyPath(GrContext*,
+                              bool hasUserStencilSettings,
+                              const GrRenderTargetContext*,
+                              const GrReducedClip&);
 
     SkIPoint                 fOrigin;
     sk_sp<const SkClipStack> fStack;
diff --git a/src/gpu/GrColorSpaceXform.cpp b/src/gpu/GrColorSpaceXform.cpp
index cd8dc33..7690297 100644
--- a/src/gpu/GrColorSpaceXform.cpp
+++ b/src/gpu/GrColorSpaceXform.cpp
@@ -121,6 +121,7 @@
 GrColor4f GrColorSpaceXform::apply(const GrColor4f& srcColor) {
     GrColor4f result;
     fSrcToDst.mapScalars(srcColor.fRGBA, result.fRGBA);
+    // We always operate on unpremul colors, so clamp to [0,1].
     for (int i = 0; i < 4; ++i) {
         result.fRGBA[i] = SkTPin(result.fRGBA[i], 0.0f, 1.0f);
     }
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 8cbe701..02b50c2 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -244,10 +244,12 @@
     return srcPI.convertPixelsTo(&dstPI, width, height);
 }
 
-bool GrContext::writeSurfacePixels(GrSurface* surface,
+bool GrContext::writeSurfacePixels(GrSurface* surface, SkColorSpace* dstColorSpace,
                                    int left, int top, int width, int height,
-                                   GrPixelConfig srcConfig, const void* buffer, size_t rowBytes,
-                                   uint32_t pixelOpsFlags) {
+                                   GrPixelConfig srcConfig, SkColorSpace* srcColorSpace,
+                                   const void* buffer, size_t rowBytes, uint32_t pixelOpsFlags) {
+    // TODO: Color space conversion
+
     ASSERT_SINGLE_OWNER
     RETURN_FALSE_IF_ABANDONED
     ASSERT_OWNED_RESOURCE(surface);
@@ -365,7 +367,8 @@
             paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
             paint.setAllowSRGBInputs(true);
             SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
-            renderTargetContext->drawRect(GrNoClip(), paint, GrAA::kNo, matrix, rect, nullptr);
+            renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix, rect,
+                                          nullptr);
 
             if (kFlushWrites_PixelOp & pixelOpsFlags) {
                 this->flushSurfaceWrites(surface);
@@ -389,10 +392,12 @@
     return true;
 }
 
-bool GrContext::readSurfacePixels(GrSurface* src,
+bool GrContext::readSurfacePixels(GrSurface* src, SkColorSpace* srcColorSpace,
                                   int left, int top, int width, int height,
-                                  GrPixelConfig dstConfig, void* buffer, size_t rowBytes,
-                                  uint32_t flags) {
+                                  GrPixelConfig dstConfig, SkColorSpace* dstColorSpace,
+                                  void* buffer, size_t rowBytes, uint32_t flags) {
+    // TODO: Color space conversion
+
     ASSERT_SINGLE_OWNER
     RETURN_FALSE_IF_ABANDONED
     ASSERT_OWNED_RESOURCE(src);
@@ -484,7 +489,8 @@
                 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
                 paint.setAllowSRGBInputs(true);
                 SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
-                tempRTC->drawRect(GrNoClip(), paint, GrAA::kNo, SkMatrix::I(), rect, nullptr);
+                tempRTC->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect,
+                                  nullptr);
                 surfaceToRead.reset(tempRTC->asTexture().release());
                 left = 0;
                 top = 0;
@@ -676,7 +682,7 @@
     static const GrPixelConfig kFallback[] = {
         kUnknown_GrPixelConfig,        // kUnknown_GrPixelConfig
         kRGBA_8888_GrPixelConfig,      // kAlpha_8_GrPixelConfig
-        kUnknown_GrPixelConfig,        // kIndex_8_GrPixelConfig
+        kUnknown_GrPixelConfig,        // kGray_8_GrPixelConfig
         kRGBA_8888_GrPixelConfig,      // kRGB_565_GrPixelConfig
         kRGBA_8888_GrPixelConfig,      // kRGBA_4444_GrPixelConfig
         kUnknown_GrPixelConfig,        // kRGBA_8888_GrPixelConfig
@@ -696,7 +702,7 @@
 
     GR_STATIC_ASSERT(0  == kUnknown_GrPixelConfig);
     GR_STATIC_ASSERT(1  == kAlpha_8_GrPixelConfig);
-    GR_STATIC_ASSERT(2  == kIndex_8_GrPixelConfig);
+    GR_STATIC_ASSERT(2  == kGray_8_GrPixelConfig);
     GR_STATIC_ASSERT(3  == kRGB_565_GrPixelConfig);
     GR_STATIC_ASSERT(4  == kRGBA_4444_GrPixelConfig);
     GR_STATIC_ASSERT(5  == kRGBA_8888_GrPixelConfig);
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index 406fe6a..9da0ffe 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -35,11 +35,9 @@
                                              const SkMatrix& viewMatrix,
                                              const SkMatrix& localMatrix,
                                              bool localCoordsWillBeRead,
-                                             bool coverageWillBeIgnored,
                                              uint8_t coverage) {
         return sk_sp<GrGeometryProcessor>(new DefaultGeoProc(
-                gpTypeFlags, color, viewMatrix, localMatrix, coverage,
-                localCoordsWillBeRead, coverageWillBeIgnored));
+                gpTypeFlags, color, viewMatrix, localMatrix, coverage, localCoordsWillBeRead));
     }
 
     const char* name() const override { return "DefaultGeometryProcessor"; }
@@ -49,13 +47,11 @@
     const Attribute* inLocalCoords() const { return fInLocalCoords; }
     const Attribute* inCoverage() const { return fInCoverage; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     bool hasVertexColor() const { return SkToBool(fInColor); }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool localCoordsWillBeRead() const { return fLocalCoordsWillBeRead; }
     uint8_t coverage() const { return fCoverage; }
-    bool coverageWillBeIgnored() const { return fCoverageWillBeIgnored; }
     bool hasVertexCoverage() const { return SkToBool(fInCoverage); }
 
     class GLSLProcessor : public GrGLSLGeometryProcessor {
@@ -74,13 +70,11 @@
             varyingHandler->emitAttributes(gp);
 
             // Setup pass through color
-            if (!gp.colorIgnored()) {
-                if (gp.hasVertexColor()) {
-                    varyingHandler->addPassThroughAttribute(gp.inColor(), args.fOutputColor);
-                } else {
-                    this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor,
-                                            &fColorUniform);
-                }
+            if (gp.hasVertexColor()) {
+                varyingHandler->addPassThroughAttribute(gp.inColor(), args.fOutputColor);
+            } else {
+                this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor,
+                                        &fColorUniform);
             }
 
             // Setup position
@@ -112,22 +106,20 @@
             }
 
             // Setup coverage as pass through
-            if (!gp.coverageWillBeIgnored()) {
-                if (gp.hasVertexCoverage()) {
-                    fragBuilder->codeAppendf("float alpha = 1.0;");
-                    varyingHandler->addPassThroughAttribute(gp.inCoverage(), "alpha");
-                    fragBuilder->codeAppendf("%s = vec4(alpha);", args.fOutputCoverage);
-                } else if (gp.coverage() == 0xff) {
-                    fragBuilder->codeAppendf("%s = vec4(1);", args.fOutputCoverage);
-                } else {
-                    const char* fragCoverage;
-                    fCoverageUniform = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                                  kFloat_GrSLType,
-                                                                  kDefault_GrSLPrecision,
-                                                                  "Coverage",
-                                                                  &fragCoverage);
-                    fragBuilder->codeAppendf("%s = vec4(%s);", args.fOutputCoverage, fragCoverage);
-                }
+            if (gp.hasVertexCoverage()) {
+                fragBuilder->codeAppendf("float alpha = 1.0;");
+                varyingHandler->addPassThroughAttribute(gp.inCoverage(), "alpha");
+                fragBuilder->codeAppendf("%s = vec4(alpha);", args.fOutputCoverage);
+            } else if (gp.coverage() == 0xff) {
+                fragBuilder->codeAppendf("%s = vec4(1);", args.fOutputCoverage);
+            } else {
+                const char* fragCoverage;
+                fCoverageUniform = uniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                              kFloat_GrSLType,
+                                                              kDefault_GrSLPrecision,
+                                                              "Coverage",
+                                                              &fragCoverage);
+                fragBuilder->codeAppendf("%s = vec4(%s);", args.fOutputCoverage, fragCoverage);
             }
         }
 
@@ -136,13 +128,11 @@
                                   GrProcessorKeyBuilder* b) {
             const DefaultGeoProc& def = gp.cast<DefaultGeoProc>();
             uint32_t key = def.fFlags;
-            key |= def.colorIgnored() << 8;
-            key |= def.coverageWillBeIgnored() << 9;
-            key |= def.hasVertexColor() << 10;
-            key |= def.hasVertexCoverage() << 11;
-            key |= def.coverage() == 0xff ? 0x1 << 12 : 0;
-            key |= def.localCoordsWillBeRead() && def.localMatrix().hasPerspective() ? 0x1 << 24 :
-                                                                                       0x0;
+            key |= def.hasVertexColor() << 8;
+            key |= def.hasVertexCoverage() << 9;
+            key |= (def.coverage() == 0xff) ? (0x1 << 10) : 0;
+            key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? (0x1 << 24)
+                                                                                       : 0x0;
             key |= ComputePosKey(def.viewMatrix()) << 25;
             b->add32(key);
         }
@@ -166,8 +156,7 @@
                 fColor = dgp.color();
             }
 
-            if (!dgp.coverageWillBeIgnored() &&
-                dgp.coverage() != fCoverage && !dgp.hasVertexCoverage()) {
+            if (dgp.coverage() != fCoverage && !dgp.hasVertexCoverage()) {
                 pdman.set1f(fCoverageUniform, GrNormalizeByteToFloat(dgp.coverage()));
                 fCoverage = dgp.coverage();
             }
@@ -199,19 +188,17 @@
                    const SkMatrix& viewMatrix,
                    const SkMatrix& localMatrix,
                    uint8_t coverage,
-                   bool localCoordsWillBeRead,
-                   bool coverageWillBeIgnored)
-        : fInPosition(nullptr)
-        , fInColor(nullptr)
-        , fInLocalCoords(nullptr)
-        , fInCoverage(nullptr)
-        , fColor(color)
-        , fViewMatrix(viewMatrix)
-        , fLocalMatrix(localMatrix)
-        , fCoverage(coverage)
-        , fFlags(gpTypeFlags)
-        , fLocalCoordsWillBeRead(localCoordsWillBeRead)
-        , fCoverageWillBeIgnored(coverageWillBeIgnored) {
+                   bool localCoordsWillBeRead)
+            : fInPosition(nullptr)
+            , fInColor(nullptr)
+            , fInLocalCoords(nullptr)
+            , fInCoverage(nullptr)
+            , fColor(color)
+            , fViewMatrix(viewMatrix)
+            , fLocalMatrix(localMatrix)
+            , fCoverage(coverage)
+            , fFlags(gpTypeFlags)
+            , fLocalCoordsWillBeRead(localCoordsWillBeRead) {
         this->initClassID<DefaultGeoProc>();
         bool hasColor = SkToBool(gpTypeFlags & kColor_GPFlag);
         bool hasExplicitLocalCoords = SkToBool(gpTypeFlags & kLocalCoord_GPFlag);
@@ -241,7 +228,6 @@
     uint8_t fCoverage;
     uint32_t fFlags;
     bool fLocalCoordsWillBeRead;
-    bool fCoverageWillBeIgnored;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
 
@@ -267,7 +253,6 @@
                                 GrTest::TestMatrix(d->fRandom),
                                 GrTest::TestMatrix(d->fRandom),
                                 d->fRandom->nextBool(),
-                                d->fRandom->nextBool(),
                                 GrRandomCoverage(d->fRandom));
 }
 
@@ -281,7 +266,6 @@
     flags |= localCoords.fType == LocalCoords::kHasExplicit_Type ? kLocalCoord_GPFlag : 0;
 
     uint8_t inCoverage = coverage.fCoverage;
-    bool coverageWillBeIgnored = coverage.fType == Coverage::kNone_Type;
     bool localCoordsWillBeRead = localCoords.fType != LocalCoords::kUnused_Type;
 
     GrColor inColor = color.fColor;
@@ -290,7 +274,6 @@
                                 viewMatrix,
                                 localCoords.fMatrix ? *localCoords.fMatrix : SkMatrix::I(),
                                 localCoordsWillBeRead,
-                                coverageWillBeIgnored,
                                 inCoverage);
 }
 
diff --git a/src/gpu/GrDefaultGeoProcFactory.h b/src/gpu/GrDefaultGeoProcFactory.h
index 688fcc9..238c154 100644
--- a/src/gpu/GrDefaultGeoProcFactory.h
+++ b/src/gpu/GrDefaultGeoProcFactory.h
@@ -66,7 +66,7 @@
             kUniform_Type,
             kAttribute_Type,
         };
-        Color(GrColor color) : fType(kUniform_Type), fColor(color) {}
+        explicit Color(GrColor color) : fType(kUniform_Type), fColor(color) {}
         Color(Type type) : fType(type), fColor(GrColor_ILLEGAL) {
             SkASSERT(type != kUniform_Type);
 
@@ -82,12 +82,11 @@
 
     struct Coverage {
         enum Type {
-            kNone_Type,
             kSolid_Type,
             kUniform_Type,
             kAttribute_Type,
         };
-        Coverage(uint8_t coverage) : fType(kUniform_Type), fCoverage(coverage) {}
+        explicit Coverage(uint8_t coverage) : fType(kUniform_Type), fCoverage(coverage) {}
         Coverage(Type type) : fType(type), fCoverage(0xff) {
             SkASSERT(type != kUniform_Type);
         }
@@ -127,8 +126,6 @@
                                                   const Coverage&,
                                                   const LocalCoords&,
                                                   const SkMatrix& viewMatrix);
-
-    inline size_t DefaultVertexStride() { return sizeof(PositionAttr); }
 };
 
 #endif
diff --git a/src/gpu/GrDistanceFieldGenFromVector.cpp b/src/gpu/GrDistanceFieldGenFromVector.cpp
new file mode 100644
index 0000000..4a509e6
--- /dev/null
+++ b/src/gpu/GrDistanceFieldGenFromVector.cpp
@@ -0,0 +1,875 @@
+/*
+ * Copyright 2017 ARM Ltd.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDistanceFieldGen.h"
+#include "GrDistanceFieldGenFromVector.h"
+
+#include "GrConfig.h"
+#include "GrPathUtils.h"
+#include "SkAutoMalloc.h"
+#include "SkGeometry.h"
+#include "SkMatrix.h"
+#include "SkPathOps.h"
+#include "SkPoint.h"
+
+/**
+ * If a scanline (a row of texel) cross from the kRight_SegSide
+ * of a segment to the kLeft_SegSide, the winding score should
+ * add 1.
+ * And winding score should subtract 1 if the scanline cross
+ * from kLeft_SegSide to kRight_SegSide.
+ * Always return kNA_SegSide if the scanline does not cross over
+ * the segment. Winding score should be zero in this case.
+ * You can get the winding number for each texel of the scanline
+ * by adding the winding score from left to right.
+ * Assuming we always start from outside, so the winding number
+ * should always start from zero.
+ *      ________         ________
+ *     |        |       |        |
+ * ...R|L......L|R.....L|R......R|L..... <= Scanline & side of segment
+ *     |+1      |-1     |-1      |+1     <= Winding score
+ *   0 |   1    ^   0   ^  -1    |0      <= Winding number
+ *     |________|       |________|
+ *
+ * .......NA................NA..........
+ *         0                 0
+ */
+enum SegSide {
+    kLeft_SegSide  = -1,
+    kOn_SegSide    =  0,
+    kRight_SegSide =  1,
+    kNA_SegSide    =  2,
+};
+
+struct DFData {
+    float fDistSq;            // distance squared to nearest (so far) edge
+    int   fDeltaWindingScore; // +1 or -1 whenever a scanline cross over a segment
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Type definition for double precision DPoint and DAffineMatrix
+ */
+
+// Point with double precision
+struct DPoint {
+    double fX, fY;
+
+    static DPoint Make(double x, double y) {
+        DPoint pt;
+        pt.set(x, y);
+        return pt;
+    }
+
+    double x() const { return fX; }
+    double y() const { return fY; }
+
+    void set(double x, double y) { fX = x; fY = y; }
+
+    /** Returns the euclidian distance from (0,0) to (x,y)
+    */
+    static double Length(double x, double y) {
+        return sqrt(x * x + y * y);
+    }
+
+    /** Returns the euclidian distance between a and b
+    */
+    static double Distance(const DPoint& a, const DPoint& b) {
+        return Length(a.fX - b.fX, a.fY - b.fY);
+    }
+
+    double distanceToSqd(const DPoint& pt) const {
+        double dx = fX - pt.fX;
+        double dy = fY - pt.fY;
+        return dx * dx + dy * dy;
+    }
+};
+
+// Matrix with double precision for affine transformation.
+// We don't store row 3 because its always (0, 0, 1).
+class DAffineMatrix {
+public:
+    double operator[](int index) const {
+        SkASSERT((unsigned)index < 6);
+        return fMat[index];
+    }
+
+    double& operator[](int index) {
+        SkASSERT((unsigned)index < 6);
+        return fMat[index];
+    }
+
+    void setAffine(double m11, double m12, double m13,
+                   double m21, double m22, double m23) {
+        fMat[0] = m11;
+        fMat[1] = m12;
+        fMat[2] = m13;
+        fMat[3] = m21;
+        fMat[4] = m22;
+        fMat[5] = m23;
+    }
+
+    /** Set the matrix to identity
+    */
+    void reset() {
+        fMat[0] = fMat[4] = 1.0;
+        fMat[1] = fMat[3] =
+        fMat[2] = fMat[5] = 0.0;
+    }
+
+    // alias for reset()
+    void setIdentity() { this->reset(); }
+
+    DPoint mapPoint(const SkPoint& src) const {
+        DPoint pt = DPoint::Make(src.x(), src.y());
+        return this->mapPoint(pt);
+    }
+
+    DPoint mapPoint(const DPoint& src) const {
+        return DPoint::Make(fMat[0] * src.x() + fMat[1] * src.y() + fMat[2],
+                            fMat[3] * src.x() + fMat[4] * src.y() + fMat[5]);
+    }
+private:
+    double fMat[6];
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const double kClose = (SK_Scalar1 / 16.0);
+static const double kCloseSqd = SkScalarMul(kClose, kClose);
+static const double kNearlyZero = (SK_Scalar1 / (1 << 18));
+static const double kTangentTolerance = (SK_Scalar1 / (1 << 11));
+static const float  kConicTolerance = 0.25f;
+
+static inline bool between_closed_open(double a, double b, double c,
+                                       double tolerance = 0.0,
+                                       bool xformToleranceToX = false) {
+    SkASSERT(tolerance >= 0.0);
+    double tolB = tolerance;
+    double tolC = tolerance;
+
+    if (xformToleranceToX) {
+        // Canonical space is y = x^2 and the derivative of x^2 is 2x.
+        // So the slope of the tangent line at point (x, x^2) is 2x.
+        //
+        //                          /|
+        //  sqrt(2x * 2x + 1 * 1)  / | 2x
+        //                        /__|
+        //                         1
+        tolB = tolerance / sqrt(4.0 * b * b + 1.0);
+        tolC = tolerance / sqrt(4.0 * c * c + 1.0);
+    }
+    return b < c ? (a >= b - tolB && a < c - tolC) :
+                   (a >= c - tolC && a < b - tolB);
+}
+
+static inline bool between_closed(double a, double b, double c,
+                                  double tolerance = 0.0,
+                                  bool xformToleranceToX = false) {
+    SkASSERT(tolerance >= 0.0);
+    double tolB = tolerance;
+    double tolC = tolerance;
+
+    if (xformToleranceToX) {
+        tolB = tolerance / sqrt(4.0 * b * b + 1.0);
+        tolC = tolerance / sqrt(4.0 * c * c + 1.0);
+    }
+    return b < c ? (a >= b - tolB && a <= c + tolC) :
+                   (a >= c - tolC && a <= b + tolB);
+}
+
+static inline bool nearly_zero(double x, double tolerance = kNearlyZero) {
+    SkASSERT(tolerance >= 0.0);
+    return fabs(x) <= tolerance;
+}
+
+static inline bool nearly_equal(double x, double y,
+                                double tolerance = kNearlyZero,
+                                bool xformToleranceToX = false) {
+    SkASSERT(tolerance >= 0.0);
+    if (xformToleranceToX) {
+        tolerance = tolerance / sqrt(4.0 * y * y + 1.0);
+    }
+    return fabs(x - y) <= tolerance;
+}
+
+static inline double sign_of(const double &val) {
+    return (val < 0.0) ? -1.0 : 1.0;
+}
+
+static bool is_colinear(const SkPoint pts[3]) {
+    return nearly_zero((pts[1].y() - pts[0].y()) * (pts[1].x() - pts[2].x()) -
+                       (pts[1].y() - pts[2].y()) * (pts[1].x() - pts[0].x()), kCloseSqd);
+}
+
+class PathSegment {
+public:
+    enum {
+        // These enum values are assumed in member functions below.
+        kLine = 0,
+        kQuad = 1,
+    } fType;
+
+    // line uses 2 pts, quad uses 3 pts
+    SkPoint fPts[3];
+
+    DPoint  fP0T, fP2T;
+    DAffineMatrix fXformMatrix;
+    double fScalingFactor;
+    double fScalingFactorSqd;
+    double fNearlyZeroScaled;
+    double fTangentTolScaledSqd;
+    SkRect  fBoundingBox;
+
+    void init();
+
+    int countPoints() {
+        GR_STATIC_ASSERT(0 == kLine && 1 == kQuad);
+        return fType + 2;
+    }
+
+    const SkPoint& endPt() const {
+        GR_STATIC_ASSERT(0 == kLine && 1 == kQuad);
+        return fPts[fType + 1];
+    }
+};
+
+typedef SkTArray<PathSegment, true> PathSegmentArray;
+
+void PathSegment::init() {
+    const DPoint p0 = DPoint::Make(fPts[0].x(), fPts[0].y());
+    const DPoint p2 = DPoint::Make(this->endPt().x(), this->endPt().y());
+    const double p0x = p0.x();
+    const double p0y = p0.y();
+    const double p2x = p2.x();
+    const double p2y = p2.y();
+
+    fBoundingBox.set(fPts[0], this->endPt());
+
+    if (fType == PathSegment::kLine) {
+        fScalingFactorSqd = fScalingFactor = 1.0;
+        double hypotenuse = DPoint::Distance(p0, p2);
+
+        const double cosTheta = (p2x - p0x) / hypotenuse;
+        const double sinTheta = (p2y - p0y) / hypotenuse;
+
+        fXformMatrix.setAffine(
+            cosTheta, sinTheta, -(cosTheta * p0x) - (sinTheta * p0y),
+            -sinTheta, cosTheta, (sinTheta * p0x) - (cosTheta * p0y)
+        );
+    } else {
+        SkASSERT(fType == PathSegment::kQuad);
+
+        // Calculate bounding box
+        const SkPoint _P1mP0 = fPts[1] - fPts[0];
+        SkPoint t = _P1mP0 - fPts[2] + fPts[1];
+        t.fX = _P1mP0.x() / t.x();
+        t.fY = _P1mP0.y() / t.y();
+        t.fX = SkScalarClampMax(t.x(), 1.0);
+        t.fY = SkScalarClampMax(t.y(), 1.0);
+        t.fX = _P1mP0.x() * t.x();
+        t.fY = _P1mP0.y() * t.y();
+        const SkPoint m = fPts[0] + t;
+        fBoundingBox.growToInclude(&m, 1);
+
+        const double p1x = fPts[1].x();
+        const double p1y = fPts[1].y();
+
+        const double p0xSqd = p0x * p0x;
+        const double p0ySqd = p0y * p0y;
+        const double p2xSqd = p2x * p2x;
+        const double p2ySqd = p2y * p2y;
+        const double p1xSqd = p1x * p1x;
+        const double p1ySqd = p1y * p1y;
+
+        const double p01xProd = p0x * p1x;
+        const double p02xProd = p0x * p2x;
+        const double b12xProd = p1x * p2x;
+        const double p01yProd = p0y * p1y;
+        const double p02yProd = p0y * p2y;
+        const double b12yProd = p1y * p2y;
+
+        const double sqrtA = p0y - (2.0 * p1y) + p2y;
+        const double a = sqrtA * sqrtA;
+        const double h = -1.0 * (p0y - (2.0 * p1y) + p2y) * (p0x - (2.0 * p1x) + p2x);
+        const double sqrtB = p0x - (2.0 * p1x) + p2x;
+        const double b = sqrtB * sqrtB;
+        const double c = (p0xSqd * p2ySqd) - (4.0 * p01xProd * b12yProd)
+                - (2.0 * p02xProd * p02yProd) + (4.0 * p02xProd * p1ySqd)
+                + (4.0 * p1xSqd * p02yProd) - (4.0 * b12xProd * p01yProd)
+                + (p2xSqd * p0ySqd);
+        const double g = (p0x * p02yProd) - (2.0 * p0x * p1ySqd)
+                + (2.0 * p0x * b12yProd) - (p0x * p2ySqd)
+                + (2.0 * p1x * p01yProd) - (4.0 * p1x * p02yProd)
+                + (2.0 * p1x * b12yProd) - (p2x * p0ySqd)
+                + (2.0 * p2x * p01yProd) + (p2x * p02yProd)
+                - (2.0 * p2x * p1ySqd);
+        const double f = -((p0xSqd * p2y) - (2.0 * p01xProd * p1y)
+                - (2.0 * p01xProd * p2y) - (p02xProd * p0y)
+                + (4.0 * p02xProd * p1y) - (p02xProd * p2y)
+                + (2.0 * p1xSqd * p0y) + (2.0 * p1xSqd * p2y)
+                - (2.0 * b12xProd * p0y) - (2.0 * b12xProd * p1y)
+                + (p2xSqd * p0y));
+
+        const double cosTheta = sqrt(a / (a + b));
+        const double sinTheta = -1.0 * sign_of((a + b) * h) * sqrt(b / (a + b));
+
+        const double gDef = cosTheta * g - sinTheta * f;
+        const double fDef = sinTheta * g + cosTheta * f;
+
+
+        const double x0 = gDef / (a + b);
+        const double y0 = (1.0 / (2.0 * fDef)) * (c - (gDef * gDef / (a + b)));
+
+
+        const double lambda = -1.0 * ((a + b) / (2.0 * fDef));
+        fScalingFactor = fabs(1.0 / lambda);
+        fScalingFactorSqd = fScalingFactor * fScalingFactor;
+
+        const double lambda_cosTheta = lambda * cosTheta;
+        const double lambda_sinTheta = lambda * sinTheta;
+
+        fXformMatrix.setAffine(
+            lambda_cosTheta, -lambda_sinTheta, lambda * x0,
+            lambda_sinTheta, lambda_cosTheta, lambda * y0
+        );
+    }
+
+    fNearlyZeroScaled = kNearlyZero / fScalingFactor;
+    fTangentTolScaledSqd = kTangentTolerance * kTangentTolerance / fScalingFactorSqd;
+
+    fP0T = fXformMatrix.mapPoint(p0);
+    fP2T = fXformMatrix.mapPoint(p2);
+}
+
+static void init_distances(DFData* data, int size) {
+    DFData* currData = data;
+
+    for (int i = 0; i < size; ++i) {
+        // init distance to "far away"
+        currData->fDistSq = SK_DistanceFieldMagnitude * SK_DistanceFieldMagnitude;
+        currData->fDeltaWindingScore = 0;
+        ++currData;
+    }
+}
+
+static inline void add_line_to_segment(const SkPoint pts[2],
+                                       PathSegmentArray* segments) {
+    segments->push_back();
+    segments->back().fType = PathSegment::kLine;
+    segments->back().fPts[0] = pts[0];
+    segments->back().fPts[1] = pts[1];
+
+    segments->back().init();
+}
+
+static inline void add_quad_segment(const SkPoint pts[3],
+                                    PathSegmentArray* segments) {
+    if (pts[0].distanceToSqd(pts[1]) < kCloseSqd ||
+        pts[1].distanceToSqd(pts[2]) < kCloseSqd ||
+        is_colinear(pts)) {
+        if (pts[0] != pts[2]) {
+            SkPoint line_pts[2];
+            line_pts[0] = pts[0];
+            line_pts[1] = pts[2];
+            add_line_to_segment(line_pts, segments);
+        }
+    } else {
+        segments->push_back();
+        segments->back().fType = PathSegment::kQuad;
+        segments->back().fPts[0] = pts[0];
+        segments->back().fPts[1] = pts[1];
+        segments->back().fPts[2] = pts[2];
+
+        segments->back().init();
+    }
+}
+
+static inline void add_cubic_segments(const SkPoint pts[4],
+                                      PathSegmentArray* segments) {
+    SkSTArray<15, SkPoint, true> quads;
+    GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, &quads);
+    int count = quads.count();
+    for (int q = 0; q < count; q += 3) {
+        add_quad_segment(&quads[q], segments);
+    }
+}
+
+static float calculate_nearest_point_for_quad(
+                const PathSegment& segment,
+                const DPoint &xFormPt) {
+    static const float kThird = 0.33333333333f;
+    static const float kTwentySeventh = 0.037037037f;
+
+    const float a = 0.5f - (float)xFormPt.y();
+    const float b = -0.5f * (float)xFormPt.x();
+
+    const float a3 = a * a * a;
+    const float b2 = b * b;
+
+    const float c = (b2 * 0.25f) + (a3 * kTwentySeventh);
+
+    if (c >= 0.f) {
+        const float sqrtC = sqrt(c);
+        const float result = (float)cbrt((-b * 0.5f) + sqrtC) + (float)cbrt((-b * 0.5f) - sqrtC);
+        return result;
+    } else {
+        const float cosPhi = (float)sqrt((b2 * 0.25f) * (-27.f / a3)) * ((b > 0) ? -1.f : 1.f);
+        const float phi = (float)acos(cosPhi);
+        float result;
+        if (xFormPt.x() > 0.f) {
+            result = 2.f * (float)sqrt(-a * kThird) * (float)cos(phi * kThird);
+            if (!between_closed(result, segment.fP0T.x(), segment.fP2T.x())) {
+                result = 2.f * (float)sqrt(-a * kThird) * (float)cos((phi * kThird) + (SK_ScalarPI * 2.f * kThird));
+            }
+        } else {
+            result = 2.f * (float)sqrt(-a * kThird) * (float)cos((phi * kThird) + (SK_ScalarPI * 2.f * kThird));
+            if (!between_closed(result, segment.fP0T.x(), segment.fP2T.x())) {
+                result = 2.f * (float)sqrt(-a * kThird) * (float)cos(phi * kThird);
+            }
+        }
+        return result;
+    }
+}
+
+// This structure contains some intermediate values shared by the same row.
+// It is used to calculate segment side of a quadratic bezier.
+struct RowData {
+    // The intersection type of a scanline and y = x * x parabola in canonical space.
+    enum IntersectionType {
+        kNoIntersection,
+        kVerticalLine,
+        kTangentLine,
+        kTwoPointsIntersect
+    } fIntersectionType;
+
+    // The direction of the quadratic segment/scanline in the canonical space.
+    //  1: The quadratic segment/scanline going from negative x-axis to positive x-axis.
+    //  0: The scanline is a vertical line in the canonical space.
+    // -1: The quadratic segment/scanline going from positive x-axis to negative x-axis.
+    int fQuadXDirection;
+    int fScanlineXDirection;
+
+    // The y-value(equal to x*x) of intersection point for the kVerticalLine intersection type.
+    double fYAtIntersection;
+
+    // The x-value for two intersection points.
+    double fXAtIntersection1;
+    double fXAtIntersection2;
+};
+
+void precomputation_for_row(
+            RowData *rowData,
+            const PathSegment& segment,
+            const SkPoint& pointLeft,
+            const SkPoint& pointRight
+            ) {
+    if (segment.fType != PathSegment::kQuad) {
+        return;
+    }
+
+    const DPoint& xFormPtLeft = segment.fXformMatrix.mapPoint(pointLeft);
+    const DPoint& xFormPtRight = segment.fXformMatrix.mapPoint(pointRight);;
+
+    rowData->fQuadXDirection = (int)sign_of(segment.fP2T.x() - segment.fP0T.x());
+    rowData->fScanlineXDirection = (int)sign_of(xFormPtRight.x() - xFormPtLeft.x());
+
+    const double x1 = xFormPtLeft.x();
+    const double y1 = xFormPtLeft.y();
+    const double x2 = xFormPtRight.x();
+    const double y2 = xFormPtRight.y();
+
+    if (nearly_equal(x1, x2, segment.fNearlyZeroScaled, true)) {
+        rowData->fIntersectionType = RowData::kVerticalLine;
+        rowData->fYAtIntersection = x1 * x1;
+        rowData->fScanlineXDirection = 0;
+        return;
+    }
+
+    // Line y = mx + b
+    const double m = (y2 - y1) / (x2 - x1);
+    const double b = -m * x1 + y1;
+
+    const double m2 = m * m;
+    const double c = m2 + 4.0 * b;
+
+    const double tol = 4.0 * segment.fTangentTolScaledSqd / (m2 + 1.0);
+
+    // Check if the scanline is the tangent line of the curve,
+    // and the curve start or end at the same y-coordinate of the scanline
+    if ((rowData->fScanlineXDirection == 1 &&
+         (segment.fPts[0].y() == pointLeft.y() ||
+         segment.fPts[2].y() == pointLeft.y())) &&
+         nearly_zero(c, tol)) {
+        rowData->fIntersectionType = RowData::kTangentLine;
+        rowData->fXAtIntersection1 = m / 2.0;
+        rowData->fXAtIntersection2 = m / 2.0;
+    } else if (c <= 0.0) {
+        rowData->fIntersectionType = RowData::kNoIntersection;
+        return;
+    } else {
+        rowData->fIntersectionType = RowData::kTwoPointsIntersect;
+        const double d = sqrt(c);
+        rowData->fXAtIntersection1 = (m + d) / 2.0;
+        rowData->fXAtIntersection2 = (m - d) / 2.0;
+    }
+}
+
+SegSide calculate_side_of_quad(
+            const PathSegment& segment,
+            const SkPoint& point,
+            const DPoint& xFormPt,
+            const RowData& rowData) {
+    SegSide side = kNA_SegSide;
+
+    if (RowData::kVerticalLine == rowData.fIntersectionType) {
+        side = (SegSide)(int)(sign_of(xFormPt.y() - rowData.fYAtIntersection) * rowData.fQuadXDirection);
+    }
+    else if (RowData::kTwoPointsIntersect == rowData.fIntersectionType) {
+        const double p1 = rowData.fXAtIntersection1;
+        const double p2 = rowData.fXAtIntersection2;
+
+        int signP1 = (int)sign_of(p1 - xFormPt.x());
+        bool includeP1 = true;
+        bool includeP2 = true;
+
+        if (rowData.fScanlineXDirection == 1) {
+            if ((rowData.fQuadXDirection == -1 && segment.fPts[0].y() <= point.y() &&
+                 nearly_equal(segment.fP0T.x(), p1, segment.fNearlyZeroScaled, true)) ||
+                 (rowData.fQuadXDirection == 1 && segment.fPts[2].y() <= point.y() &&
+                 nearly_equal(segment.fP2T.x(), p1, segment.fNearlyZeroScaled, true))) {
+                includeP1 = false;
+            }
+            if ((rowData.fQuadXDirection == -1 && segment.fPts[2].y() <= point.y() &&
+                 nearly_equal(segment.fP2T.x(), p2, segment.fNearlyZeroScaled, true)) ||
+                 (rowData.fQuadXDirection == 1 && segment.fPts[0].y() <= point.y() &&
+                 nearly_equal(segment.fP0T.x(), p2, segment.fNearlyZeroScaled, true))) {
+                includeP2 = false;
+            }
+        }
+
+        if (includeP1 && between_closed(p1, segment.fP0T.x(), segment.fP2T.x(),
+                                        segment.fNearlyZeroScaled, true)) {
+            side = (SegSide)(signP1 * rowData.fQuadXDirection);
+        }
+        if (includeP2 && between_closed(p2, segment.fP0T.x(), segment.fP2T.x(),
+                                        segment.fNearlyZeroScaled, true)) {
+            int signP2 = (int)sign_of(p2 - xFormPt.x());
+            if (side == kNA_SegSide || signP2 == 1) {
+                side = (SegSide)(-signP2 * rowData.fQuadXDirection);
+            }
+        }
+    } else if (RowData::kTangentLine == rowData.fIntersectionType) {
+        // The scanline is the tangent line of current quadratic segment.
+
+        const double p = rowData.fXAtIntersection1;
+        int signP = (int)sign_of(p - xFormPt.x());
+        if (rowData.fScanlineXDirection == 1) {
+            // The path start or end at the tangent point.
+            if (segment.fPts[0].y() == point.y()) {
+                side = (SegSide)(signP);
+            } else if (segment.fPts[2].y() == point.y()) {
+                side = (SegSide)(-signP);
+            }
+        }
+    }
+
+    return side;
+}
+
+static float distance_to_segment(const SkPoint& point,
+                                 const PathSegment& segment,
+                                 const RowData& rowData,
+                                 SegSide* side) {
+    SkASSERT(side);
+
+    const DPoint xformPt = segment.fXformMatrix.mapPoint(point);
+
+    if (segment.fType == PathSegment::kLine) {
+        float result = SK_DistanceFieldPad * SK_DistanceFieldPad;
+
+        if (between_closed(xformPt.x(), segment.fP0T.x(), segment.fP2T.x())) {
+            result = (float)(xformPt.y() * xformPt.y());
+        } else if (xformPt.x() < segment.fP0T.x()) {
+            result = (float)(xformPt.x() * xformPt.x() + xformPt.y() * xformPt.y());
+        } else {
+            result = (float)((xformPt.x() - segment.fP2T.x()) * (xformPt.x() - segment.fP2T.x())
+                     + xformPt.y() * xformPt.y());
+        }
+
+        if (between_closed_open(point.y(), segment.fBoundingBox.top(),
+                                segment.fBoundingBox.bottom())) {
+            *side = (SegSide)(int)sign_of(xformPt.y());
+        } else {
+            *side = kNA_SegSide;
+        }
+        return result;
+    } else {
+        SkASSERT(segment.fType == PathSegment::kQuad);
+
+        const float nearestPoint = calculate_nearest_point_for_quad(segment, xformPt);
+
+        float dist;
+
+        if (between_closed(nearestPoint, segment.fP0T.x(), segment.fP2T.x())) {
+            DPoint x = DPoint::Make(nearestPoint, nearestPoint * nearestPoint);
+            dist = (float)xformPt.distanceToSqd(x);
+        } else {
+            const float distToB0T = (float)xformPt.distanceToSqd(segment.fP0T);
+            const float distToB2T = (float)xformPt.distanceToSqd(segment.fP2T);
+
+            if (distToB0T < distToB2T) {
+                dist = distToB0T;
+            } else {
+                dist = distToB2T;
+            }
+        }
+
+        if (between_closed_open(point.y(), segment.fBoundingBox.top(),
+                                segment.fBoundingBox.bottom())) {
+            *side = calculate_side_of_quad(segment, point, xformPt, rowData);
+        } else {
+            *side = kNA_SegSide;
+        }
+
+        return (float)(dist * segment.fScalingFactorSqd);
+    }
+}
+
+static void calculate_distance_field_data(PathSegmentArray* segments,
+                                          DFData* dataPtr,
+                                          int width, int height) {
+    int count = segments->count();
+    for (int a = 0; a < count; ++a) {
+        PathSegment& segment = (*segments)[a];
+        const SkRect& segBB = segment.fBoundingBox.makeOutset(
+                                SK_DistanceFieldPad, SK_DistanceFieldPad);
+        int startColumn = (int)segBB.left();
+        int endColumn = SkScalarCeilToInt(segBB.right());
+
+        int startRow = (int)segBB.top();
+        int endRow = SkScalarCeilToInt(segBB.bottom());
+
+        SkASSERT((startColumn >= 0) && "StartColumn < 0!");
+        SkASSERT((endColumn <= width) && "endColumn > width!");
+        SkASSERT((startRow >= 0) && "StartRow < 0!");
+        SkASSERT((endRow <= height) && "EndRow > height!");
+
+        // Clip inside the distance field to avoid overflow
+        startColumn = SkTMax(startColumn, 0);
+        endColumn   = SkTMin(endColumn,   width);
+        startRow    = SkTMax(startRow,    0);
+        endRow      = SkTMin(endRow,      height);
+
+        for (int row = startRow; row < endRow; ++row) {
+            SegSide prevSide = kNA_SegSide;
+            const float pY = row + 0.5f;
+            RowData rowData;
+
+            const SkPoint pointLeft = SkPoint::Make((SkScalar)startColumn, pY);
+            const SkPoint pointRight = SkPoint::Make((SkScalar)endColumn, pY);
+
+            if (between_closed_open(pY, segment.fBoundingBox.top(),
+                                    segment.fBoundingBox.bottom())) {
+                precomputation_for_row(&rowData, segment, pointLeft, pointRight);
+            }
+
+            for (int col = startColumn; col < endColumn; ++col) {
+                int idx = (row * width) + col;
+
+                const float pX = col + 0.5f;
+                const SkPoint point = SkPoint::Make(pX, pY);
+
+                const float distSq = dataPtr[idx].fDistSq;
+                int dilation = distSq < 1.5 * 1.5 ? 1 :
+                               distSq < 2.5 * 2.5 ? 2 :
+                               distSq < 3.5 * 3.5 ? 3 : SK_DistanceFieldPad;
+                if (dilation > SK_DistanceFieldPad) {
+                    dilation = SK_DistanceFieldPad;
+                }
+
+                // Optimisation for not calculating some points.
+                if (dilation != SK_DistanceFieldPad && !segment.fBoundingBox.roundOut()
+                    .makeOutset(dilation, dilation).contains(col, row)) {
+                    continue;
+                }
+
+                SegSide side = kNA_SegSide;
+                int     deltaWindingScore = 0;
+                float   currDistSq = distance_to_segment(point, segment, rowData, &side);
+                if (prevSide == kLeft_SegSide && side == kRight_SegSide) {
+                    deltaWindingScore = -1;
+                } else if (prevSide == kRight_SegSide && side == kLeft_SegSide) {
+                    deltaWindingScore = 1;
+                }
+
+                prevSide = side;
+
+                if (currDistSq < distSq) {
+                    dataPtr[idx].fDistSq = currDistSq;
+                }
+
+                dataPtr[idx].fDeltaWindingScore += deltaWindingScore;
+            }
+        }
+    }
+}
+
+template <int distanceMagnitude>
+static unsigned char pack_distance_field_val(float dist) {
+    // The distance field is constructed as unsigned char values, so that the zero value is at 128,
+    // Beside 128, we have 128 values in range [0, 128), but only 127 values in range (128, 255].
+    // So we multiply distanceMagnitude by 127/128 at the latter range to avoid overflow.
+    dist = SkScalarPin(-dist, -distanceMagnitude, distanceMagnitude * 127.0f / 128.0f);
+
+    // Scale into the positive range for unsigned distance.
+    dist += distanceMagnitude;
+
+    // Scale into unsigned char range.
+    // Round to place negative and positive values as equally as possible around 128
+    // (which represents zero).
+    return (unsigned char)SkScalarRoundToInt(dist / (2 * distanceMagnitude) * 256.0f);
+}
+
+bool GrGenerateDistanceFieldFromPath(unsigned char* distanceField,
+                                     const SkPath& path, const SkMatrix& drawMatrix,
+                                     int width, int height, size_t rowBytes) {
+    SkASSERT(distanceField);
+
+    SkDEBUGCODE(SkPath xformPath;);
+    SkDEBUGCODE(path.transform(drawMatrix, &xformPath));
+    SkDEBUGCODE(SkIRect pathBounds = xformPath.getBounds().roundOut());
+    SkDEBUGCODE(SkIRect expectPathBounds = SkIRect::MakeWH(width - 2 * SK_DistanceFieldPad,
+                                                           height - 2 * SK_DistanceFieldPad));
+    SkASSERT(expectPathBounds.isEmpty() ||
+             expectPathBounds.contains(pathBounds.x(), pathBounds.y()));
+    SkASSERT(expectPathBounds.isEmpty() || pathBounds.isEmpty() ||
+             expectPathBounds.contains(pathBounds));
+
+    SkPath simplifiedPath;
+    SkPath workingPath;
+    if (Simplify(path, &simplifiedPath)) {
+        workingPath = simplifiedPath;
+    } else {
+        workingPath = path;
+    }
+
+    if (!IsDistanceFieldSupportedFillType(workingPath.getFillType())) {
+        return false;
+    }
+
+    workingPath.transform(drawMatrix);
+
+    SkDEBUGCODE(pathBounds = workingPath.getBounds().roundOut());
+    SkASSERT(expectPathBounds.isEmpty() ||
+             expectPathBounds.contains(pathBounds.x(), pathBounds.y()));
+    SkASSERT(expectPathBounds.isEmpty() || pathBounds.isEmpty() ||
+             expectPathBounds.contains(pathBounds));
+
+    // translate path to offset (SK_DistanceFieldPad, SK_DistanceFieldPad)
+    SkMatrix dfMatrix;
+    dfMatrix.setTranslate(SK_DistanceFieldPad, SK_DistanceFieldPad);
+    workingPath.transform(dfMatrix);
+
+    // create temp data
+    size_t dataSize = width * height * sizeof(DFData);
+    SkAutoSMalloc<1024> dfStorage(dataSize);
+    DFData* dataPtr = (DFData*) dfStorage.get();
+
+    // create initial distance data
+    init_distances(dataPtr, width * height);
+
+    SkPath::Iter iter(workingPath, true);
+    SkSTArray<15, PathSegment, true> segments;
+
+    for (;;) {
+        SkPoint pts[4];
+        SkPath::Verb verb = iter.next(pts);
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                break;
+            case SkPath::kLine_Verb: {
+                add_line_to_segment(pts, &segments);
+                break;
+            }
+            case SkPath::kQuad_Verb:
+                add_quad_segment(pts, &segments);
+                break;
+            case SkPath::kConic_Verb: {
+                SkScalar weight = iter.conicWeight();
+                SkAutoConicToQuads converter;
+                const SkPoint* quadPts = converter.computeQuads(pts, weight, kConicTolerance);
+                for (int i = 0; i < converter.countQuads(); ++i) {
+                    add_quad_segment(quadPts + 2*i, &segments);
+                }
+                break;
+            }
+            case SkPath::kCubic_Verb: {
+                add_cubic_segments(pts, &segments);
+                break;
+            };
+            default:
+                break;
+        }
+        if (verb == SkPath::kDone_Verb) {
+            break;
+        }
+    }
+
+    calculate_distance_field_data(&segments, dataPtr, width, height);
+
+    for (int row = 0; row < height; ++row) {
+        int windingNumber = 0; // Winding number start from zero for each scanline
+        for (int col = 0; col < width; ++col) {
+            int idx = (row * width) + col;
+            windingNumber += dataPtr[idx].fDeltaWindingScore;
+
+            enum DFSign {
+                kInside = -1,
+                kOutside = 1
+            } dfSign;
+
+            if (workingPath.getFillType() == SkPath::kWinding_FillType) {
+                dfSign = windingNumber ? kInside : kOutside;
+            } else if (workingPath.getFillType() == SkPath::kInverseWinding_FillType) {
+                dfSign = windingNumber ? kOutside : kInside;
+            } else if (workingPath.getFillType() == SkPath::kEvenOdd_FillType) {
+                dfSign = (windingNumber % 2) ? kInside : kOutside;
+            } else {
+                SkASSERT(workingPath.getFillType() == SkPath::kInverseEvenOdd_FillType);
+                dfSign = (windingNumber % 2) ? kOutside : kInside;
+            }
+
+            // The winding number at the end of a scanline should be zero.
+            SkASSERT(((col != width - 1) || (windingNumber == 0)) &&
+                    "Winding number should be zero at the end of a scan line.");
+            // Fallback to use SkPath::contains to determine the sign of pixel in release build.
+            if (col == width - 1 && windingNumber != 0) {
+                for (int col = 0; col < width; ++col) {
+                    int idx = (row * width) + col;
+                    dfSign = workingPath.contains(col + 0.5, row + 0.5) ? kInside : kOutside;
+                    const float miniDist = sqrt(dataPtr[idx].fDistSq);
+                    const float dist = dfSign * miniDist;
+
+                    unsigned char pixelVal = pack_distance_field_val<SK_DistanceFieldMagnitude>(dist);
+
+                    distanceField[(row * rowBytes) + col] = pixelVal;
+                }
+                continue;
+            }
+
+            const float miniDist = sqrt(dataPtr[idx].fDistSq);
+            const float dist = dfSign * miniDist;
+
+            unsigned char pixelVal = pack_distance_field_val<SK_DistanceFieldMagnitude>(dist);
+
+            distanceField[(row * rowBytes) + col] = pixelVal;
+        }
+    }
+    return true;
+}
diff --git a/src/gpu/GrDistanceFieldGenFromVector.h b/src/gpu/GrDistanceFieldGenFromVector.h
new file mode 100644
index 0000000..48a0784
--- /dev/null
+++ b/src/gpu/GrDistanceFieldGenFromVector.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 ARM Ltd.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrDistanceFieldGenFromVector_DEFINED
+#define GrDistanceFieldGenFromVector_DEFINED
+
+#include "SkPath.h"
+
+class SkMatrix;
+
+#ifndef SK_USE_LEGACY_DISTANCE_FIELDS
+    #define SK_USE_LEGACY_DISTANCE_FIELDS
+#endif
+
+/** Given a vector path, generate the associated distance field.
+
+ *  @param distanceField     The distance field to be generated. Should already be allocated
+ *                           by the client with the padding defined in "SkDistanceFieldGen.h".
+ *  @param path              The path we're using to generate the distance field.
+ *  @param matrix            Transformation matrix for path.
+ *  @param width             Width of the distance field.
+ *  @param height            Height of the distance field.
+ *  @param rowBytes          Size of each row in the distance field, in bytes.
+ */
+bool GrGenerateDistanceFieldFromPath(unsigned char* distanceField,
+                                     const SkPath& path, const SkMatrix& viewMatrix,
+                                     int width, int height, size_t rowBytes);
+
+inline bool IsDistanceFieldSupportedFillType(SkPath::FillType fFillType)
+{
+	return (SkPath::kEvenOdd_FillType == fFillType ||
+            SkPath::kInverseEvenOdd_FillType == fFillType);
+}
+
+#endif
diff --git a/src/gpu/GrDrawOpTest.cpp b/src/gpu/GrDrawOpTest.cpp
index bb26acc..3cf1a90 100644
--- a/src/gpu/GrDrawOpTest.cpp
+++ b/src/gpu/GrDrawOpTest.cpp
@@ -12,7 +12,8 @@
 
 #ifdef GR_TEST_UTILS
 
-#define DRAW_OP_TEST_EXTERN(Op) extern sk_sp<GrDrawOp> Op##__Test(SkRandom*, GrContext* context);
+#define DRAW_OP_TEST_EXTERN(Op) \
+    extern std::unique_ptr<GrDrawOp> Op##__Test(SkRandom*, GrContext* context);
 
 #define DRAW_OP_TEST_ENTRY(Op) Op##__Test
 
@@ -37,8 +38,8 @@
 DRAW_OP_TEST_EXTERN(TextBlobOp);
 DRAW_OP_TEST_EXTERN(VerticesOp);
 
-sk_sp<GrDrawOp> GrRandomDrawOp(SkRandom* random, GrContext* context) {
-    using MakeTestDrawOpFn = sk_sp<GrDrawOp>(SkRandom* random, GrContext* context);
+std::unique_ptr<GrDrawOp> GrRandomDrawOp(SkRandom* random, GrContext* context) {
+    using MakeTestDrawOpFn = std::unique_ptr<GrDrawOp>(SkRandom* random, GrContext* context);
     static constexpr MakeTestDrawOpFn* gFactories[] = {
         DRAW_OP_TEST_ENTRY(AAConvexPathOp),
         DRAW_OP_TEST_ENTRY(AADistanceFieldPathOp),
diff --git a/src/gpu/GrDrawOpTest.h b/src/gpu/GrDrawOpTest.h
index 0d88a40..46abf18 100644
--- a/src/gpu/GrDrawOpTest.h
+++ b/src/gpu/GrDrawOpTest.h
@@ -18,14 +18,15 @@
 class SkRandom;
 
 /**  This function returns a randomly configured GrDrawOp for testing purposes. */
-sk_sp<GrDrawOp> GrRandomDrawOp(SkRandom*, GrContext*);
+std::unique_ptr<GrDrawOp> GrRandomDrawOp(SkRandom*, GrContext*);
 
 /** GrDrawOp subclasses should define test factory functions using this macro. */
-#define DRAW_OP_TEST_DEFINE(Op) sk_sp<GrDrawOp> Op##__Test(SkRandom* random, GrContext* context)
+#define DRAW_OP_TEST_DEFINE(Op) \
+    std::unique_ptr<GrDrawOp> Op##__Test(SkRandom* random, GrContext* context)
 
 /** This macro may be used if the test factory function must be made a friend of a class. */
 #define DRAW_OP_TEST_FRIEND(Op) \
-    friend sk_sp<GrDrawOp> Op##__Test(SkRandom* random, GrContext* context);
+    friend std::unique_ptr<GrDrawOp> Op##__Test(SkRandom* random, GrContext* context);
 
 #endif
 #endif
diff --git a/src/gpu/GrPaint.cpp b/src/gpu/GrPaint.cpp
index 0954505..122d87e 100644
--- a/src/gpu/GrPaint.cpp
+++ b/src/gpu/GrPaint.cpp
@@ -12,14 +12,8 @@
 #include "effects/GrPorterDuffXferProcessor.h"
 #include "effects/GrSimpleTextureEffect.h"
 
-GrPaint::GrPaint()
-    : fDisableOutputConversionToSRGB(false)
-    , fAllowSRGBInputs(false)
-    , fUsesDistanceVectorField(false)
-    , fColor(GrColor4f::OpaqueWhite()) {}
-
 void GrPaint::setCoverageSetOpXPFactory(SkRegion::Op regionOp, bool invertCoverage) {
-    fXPFactory = GrCoverageSetOpXPFactory::Make(regionOp, invertCoverage);
+    fXPFactory = GrCoverageSetOpXPFactory::Get(regionOp, invertCoverage);
 }
 
 void GrPaint::addColorTextureProcessor(GrTexture* texture,
diff --git a/src/gpu/GrPathProcessor.cpp b/src/gpu/GrPathProcessor.cpp
index 5e1089c..39d418b 100644
--- a/src/gpu/GrPathProcessor.cpp
+++ b/src/gpu/GrPathProcessor.cpp
@@ -20,9 +20,7 @@
     static void GenKey(const GrPathProcessor& pathProc,
                        const GrShaderCaps&,
                        GrProcessorKeyBuilder* b) {
-        b->add32(SkToInt(pathProc.optimizations().readsColor()) |
-                 (SkToInt(pathProc.optimizations().readsCoverage()) << 1) |
-                 (SkToInt(pathProc.viewMatrix().hasPerspective()) << 2));
+        b->add32(SkToInt(pathProc.viewMatrix().hasPerspective()));
     }
 
     void emitCode(EmitArgs& args) override {
@@ -37,20 +35,16 @@
         this->emitTransforms(args.fVaryingHandler, args.fFPCoordTransformHandler);
 
         // Setup uniform color
-        if (pathProc.optimizations().readsColor()) {
-            const char* stagedLocalVarName;
-            fColorUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                             kVec4f_GrSLType,
-                                                             kDefault_GrSLPrecision,
-                                                             "Color",
-                                                             &stagedLocalVarName);
-            fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, stagedLocalVarName);
-        }
+        const char* stagedLocalVarName;
+        fColorUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                         kVec4f_GrSLType,
+                                                         kDefault_GrSLPrecision,
+                                                         "Color",
+                                                         &stagedLocalVarName);
+        fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, stagedLocalVarName);
 
         // setup constant solid coverage
-        if (pathProc.optimizations().readsCoverage()) {
-            fragBuilder->codeAppendf("%s = vec4(1);", args.fOutputCoverage);
-        }
+        fragBuilder->codeAppendf("%s = vec4(1);", args.fOutputCoverage);
     }
 
     void emitTransforms(GrGLSLVaryingHandler* varyingHandler,
@@ -79,7 +73,7 @@
                  const GrPrimitiveProcessor& primProc,
                  FPCoordTransformIter&& transformIter) override {
         const GrPathProcessor& pathProc = primProc.cast<GrPathProcessor>();
-        if (pathProc.optimizations().readsColor() && pathProc.color() != fColor) {
+        if (pathProc.color() != fColor) {
             float c[4];
             GrColorToRGBAFloat(pathProc.color(), c);
             pd.set4fv(fColorUniform, 1, c);
@@ -120,13 +114,11 @@
 };
 
 GrPathProcessor::GrPathProcessor(GrColor color,
-                                 const GrPipelineOptimizations& optimizations,
                                  const SkMatrix& viewMatrix,
                                  const SkMatrix& localMatrix)
         : fColor(color)
         , fViewMatrix(viewMatrix)
-        , fLocalMatrix(localMatrix)
-        , fOptimizations(optimizations) {
+        , fLocalMatrix(localMatrix) {
     this->initClassID<GrPathProcessor>();
 }
 
diff --git a/src/gpu/GrPathProcessor.h b/src/gpu/GrPathProcessor.h
index fe29d03..581bd50 100644
--- a/src/gpu/GrPathProcessor.h
+++ b/src/gpu/GrPathProcessor.h
@@ -17,10 +17,9 @@
 class GrPathProcessor : public GrPrimitiveProcessor {
 public:
     static GrPathProcessor* Create(GrColor color,
-                                   const GrPipelineOptimizations& optimizations,
                                    const SkMatrix& viewMatrix = SkMatrix::I(),
                                    const SkMatrix& localMatrix = SkMatrix::I()) {
-        return new GrPathProcessor(color, optimizations, viewMatrix, localMatrix);
+        return new GrPathProcessor(color, viewMatrix, localMatrix);
     }
 
     const char* name() const override { return "PathProcessor"; }
@@ -36,20 +35,16 @@
 
     virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override;
 
-    const GrPipelineOptimizations& optimizations() const { return fOptimizations; }
-
     virtual bool isPathRendering() const override { return true; }
 
 private:
-    GrPathProcessor(GrColor, const GrPipelineOptimizations&, const SkMatrix& viewMatrix,
-                    const SkMatrix& localMatrix);
+    GrPathProcessor(GrColor, const SkMatrix& viewMatrix, const SkMatrix& localMatrix);
 
     bool hasExplicitLocalCoords() const override { return false; }
 
     GrColor fColor;
     const SkMatrix fViewMatrix;
     const SkMatrix fLocalMatrix;
-    GrPipelineOptimizations fOptimizations;
 
     typedef GrPrimitiveProcessor INHERITED;
 };
diff --git a/src/gpu/GrPathRenderer.h b/src/gpu/GrPathRenderer.h
index 8548087..f1164a9 100644
--- a/src/gpu/GrPathRenderer.h
+++ b/src/gpu/GrPathRenderer.h
@@ -124,20 +124,18 @@
      * fGammaCorrect          true if gamma-correct rendering is to be used.
      */
     struct DrawPathArgs {
-        GrResourceProvider*         fResourceProvider;
-        const GrPaint*              fPaint;
-        const GrUserStencilSettings*fUserStencilSettings;
-
-        GrRenderTargetContext*      fRenderTargetContext;
-        const GrClip*               fClip;
-        const SkMatrix*             fViewMatrix;
-        const GrShape*              fShape;
-        GrAAType                    fAAType;
-        bool                        fGammaCorrect;
+        GrResourceProvider*          fResourceProvider;
+        GrPaint&&                    fPaint;
+        const GrUserStencilSettings* fUserStencilSettings;
+        GrRenderTargetContext*       fRenderTargetContext;
+        const GrClip*                fClip;
+        const SkMatrix*              fViewMatrix;
+        const GrShape*               fShape;
+        GrAAType                     fAAType;
+        bool                         fGammaCorrect;
 #ifdef SK_DEBUG
         void validate() const {
             SkASSERT(fResourceProvider);
-            SkASSERT(fPaint);
             SkASSERT(fUserStencilSettings);
             SkASSERT(fRenderTargetContext);
             SkASSERT(fClip);
@@ -279,15 +277,15 @@
 
         GrPaint paint;
 
-        DrawPathArgs drawArgs;
-        drawArgs.fResourceProvider = args.fResourceProvider;
-        drawArgs.fPaint = &paint;
-        drawArgs.fUserStencilSettings = &kIncrementStencil;
-        drawArgs.fRenderTargetContext = args.fRenderTargetContext;
-        drawArgs.fViewMatrix = args.fViewMatrix;
-        drawArgs.fShape = args.fShape;
-        drawArgs.fAAType = args.fAAType;
-        drawArgs.fGammaCorrect = false;
+        DrawPathArgs drawArgs{args.fResourceProvider,
+                              std::move(paint),
+                              &kIncrementStencil,
+                              args.fRenderTargetContext,
+                              nullptr,  // clip
+                              args.fViewMatrix,
+                              args.fShape,
+                              args.fAAType,
+                              false};
         this->drawPath(drawArgs);
     }
 
diff --git a/src/gpu/GrPathRenderingRenderTargetContext.cpp b/src/gpu/GrPathRenderingRenderTargetContext.cpp
index f2d092a..3319a05 100644
--- a/src/gpu/GrPathRenderingRenderTargetContext.cpp
+++ b/src/gpu/GrPathRenderingRenderTargetContext.cpp
@@ -15,7 +15,7 @@
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
 #define RETURN_IF_ABANDONED        if (this->drawingManager()->wasAbandoned()) { return; }
 
-void GrPathRenderingRenderTargetContext::drawText(const GrClip& clip, const GrPaint& grPaint,
+void GrPathRenderingRenderTargetContext::drawText(const GrClip& clip, GrPaint&& grPaint,
                                                   const SkPaint& skPaint,
                                                   const SkMatrix& viewMatrix, const char text[],
                                                   size_t byteLength, SkScalar x, SkScalar y,
@@ -30,15 +30,15 @@
         fStencilAndCoverTextContext.reset(GrStencilAndCoverTextContext::Create(fallbackContext));
     }
 
-    fStencilAndCoverTextContext->drawText(this->drawingManager()->getContext(), this, clip, grPaint,
-                                          skPaint, viewMatrix, this->surfaceProps(),
-                                          text, byteLength, x, y, clipBounds);
+    fStencilAndCoverTextContext->drawText(this->drawingManager()->getContext(), this, clip,
+                                          std::move(grPaint), skPaint, viewMatrix,
+                                          this->surfaceProps(), text, byteLength, x, y, clipBounds);
 }
 
-void GrPathRenderingRenderTargetContext::drawPosText(const GrClip& clip, const GrPaint& grPaint,
+void GrPathRenderingRenderTargetContext::drawPosText(const GrClip& clip, GrPaint&& grPaint,
                                                      const SkPaint& skPaint,
                                                      const SkMatrix& viewMatrix, const char text[],
-                                                     size_t byteLength,  const SkScalar pos[],
+                                                     size_t byteLength, const SkScalar pos[],
                                                      int scalarsPerPosition, const SkPoint& offset,
                                                      const SkIRect& clipBounds) {
     ASSERT_SINGLE_OWNER
@@ -53,9 +53,9 @@
     }
 
     fStencilAndCoverTextContext->drawPosText(this->drawingManager()->getContext(), this, clip,
-                                             grPaint, skPaint, viewMatrix, this->surfaceProps(),
-                                             text, byteLength, pos, scalarsPerPosition, offset,
-                                             clipBounds);
+                                             std::move(grPaint), skPaint, viewMatrix,
+                                             this->surfaceProps(), text, byteLength, pos,
+                                             scalarsPerPosition, offset, clipBounds);
 }
 
 void GrPathRenderingRenderTargetContext::drawTextBlob(const GrClip& clip, const SkPaint& skPaint,
diff --git a/src/gpu/GrPathRenderingRenderTargetContext.h b/src/gpu/GrPathRenderingRenderTargetContext.h
index 1ad3a0b..0597539 100644
--- a/src/gpu/GrPathRenderingRenderTargetContext.h
+++ b/src/gpu/GrPathRenderingRenderTargetContext.h
@@ -14,13 +14,13 @@
 
 class GrPathRenderingRenderTargetContext : public GrRenderTargetContext {
 public:
-    void drawText(const GrClip&,  const GrPaint&, const SkPaint&,
-                  const SkMatrix& viewMatrix, const char text[], size_t byteLength,
-                  SkScalar x, SkScalar y, const SkIRect& clipBounds) override;
-    void drawPosText(const GrClip&, const GrPaint&, const SkPaint&,
-                     const SkMatrix& viewMatrix, const char text[], size_t byteLength,
-                     const SkScalar pos[], int scalarsPerPosition,
-                     const SkPoint& offset, const SkIRect& clipBounds) override;
+    void drawText(const GrClip&, GrPaint&&, const SkPaint&, const SkMatrix& viewMatrix,
+                  const char text[], size_t byteLength, SkScalar x, SkScalar y,
+                  const SkIRect& clipBounds) override;
+    void drawPosText(const GrClip&, GrPaint&&, const SkPaint&, const SkMatrix& viewMatrix,
+                     const char text[], size_t byteLength, const SkScalar pos[],
+                     int scalarsPerPosition, const SkPoint& offset,
+                     const SkIRect& clipBounds) override;
     void drawTextBlob(const GrClip&, const SkPaint&,
                       const SkMatrix& viewMatrix, const SkTextBlob*,
                       SkScalar x, SkScalar y,
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index 2ca8e2c..ea3f0f2 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -111,9 +111,10 @@
     // information.
     int firstCoverageProcessorIdx = 0;
 
-    pipeline->adjustProgramFromOptimizations(builder, optFlags, args.fAnalysis.fColorPOI,
-                                             args.fAnalysis.fCoveragePOI, &firstColorProcessorIdx,
-                                             &firstCoverageProcessorIdx);
+    if ((optFlags & GrXferProcessor::kIgnoreColor_OptFlag) ||
+        (optFlags & GrXferProcessor::kOverrideColor_OptFlag)) {
+        firstColorProcessorIdx = builder.numColorFragmentProcessors();
+    }
 
     bool usesLocalCoords = false;
 
@@ -139,16 +140,10 @@
 
     // Setup info we need to pass to GrPrimitiveProcessors that are used with this GrPipeline.
     optimizations->fFlags = 0;
-    if (!SkToBool(optFlags & GrXferProcessor::kIgnoreColor_OptFlag)) {
-        optimizations->fFlags |= GrPipelineOptimizations::kReadsColor_Flag;
-    }
     if (GrColor_ILLEGAL != overrideColor) {
         optimizations->fFlags |= GrPipelineOptimizations::kUseOverrideColor_Flag;
         optimizations->fOverrideColor = overrideColor;
     }
-    if (!SkToBool(optFlags & GrXferProcessor::kIgnoreCoverage_OptFlag)) {
-        optimizations->fFlags |= GrPipelineOptimizations::kReadsCoverage_Flag;
-    }
     if (usesLocalCoords) {
         optimizations->fFlags |= GrPipelineOptimizations::kReadsLocalCoords_Flag;
     }
@@ -194,24 +189,6 @@
     }
 }
 
-void GrPipeline::adjustProgramFromOptimizations(const GrPipelineBuilder& pipelineBuilder,
-                                                GrXferProcessor::OptFlags flags,
-                                                const GrProcOptInfo& colorPOI,
-                                                const GrProcOptInfo& coveragePOI,
-                                                int* firstColorProcessorIdx,
-                                                int* firstCoverageProcessorIdx) {
-    fIgnoresCoverage = SkToBool(flags & GrXferProcessor::kIgnoreCoverage_OptFlag);
-
-    if ((flags & GrXferProcessor::kIgnoreColor_OptFlag) ||
-        (flags & GrXferProcessor::kOverrideColor_OptFlag)) {
-        *firstColorProcessorIdx = pipelineBuilder.numColorFragmentProcessors();
-    }
-
-    if (flags & GrXferProcessor::kIgnoreCoverage_OptFlag) {
-        *firstCoverageProcessorIdx = pipelineBuilder.numCoverageFragmentProcessors();
-    }
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 
 bool GrPipeline::AreEqual(const GrPipeline& a, const GrPipeline& b) {
@@ -224,8 +201,7 @@
         !a.fWindowRectsState.cheapEqualTo(b.fWindowRectsState) ||
         a.fFlags != b.fFlags ||
         a.fUserStencilSettings != b.fUserStencilSettings ||
-        a.fDrawFace != b.fDrawFace ||
-        a.fIgnoresCoverage != b.fIgnoresCoverage) {
+        a.fDrawFace != b.fDrawFace) {
         return false;
     }
 
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index e49dad4..0013ce6 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -199,24 +199,10 @@
     GrDrawFace getDrawFace() const { return fDrawFace; }
 
 
-    ///////////////////////////////////////////////////////////////////////////
-
-    bool ignoresCoverage() const { return fIgnoresCoverage; }
-
 private:
     GrPipeline() { /** Initialized in factory function*/ }
 
     /**
-     * Alter the program desc and inputs (attribs and processors) based on the blend optimization.
-     */
-    void adjustProgramFromOptimizations(const GrPipelineBuilder& ds,
-                                        GrXferProcessor::OptFlags,
-                                        const GrProcOptInfo& colorPOI,
-                                        const GrProcOptInfo& coveragePOI,
-                                        int* firstColorProcessorIdx,
-                                        int* firstCoverageProcessorIdx);
-
-    /**
      * Calculates the primary and secondary output types of the shader. For certain output types
      * the function may adjust the blend coefficients. After this function is called the src and dst
      * blend coeffs will represent those used by backend API.
@@ -246,7 +232,6 @@
     uint32_t                            fFlags;
     ProgramXferProcessor                fXferProcessor;
     FragmentProcessorArray              fFragmentProcessors;
-    bool                                fIgnoresCoverage;
 
     // This value is also the index in fFragmentProcessors where coverage processors begin.
     int                                 fNumColorProcessors;
diff --git a/src/gpu/GrPipelineBuilder.cpp b/src/gpu/GrPipelineBuilder.cpp
index bd433fc..fc67c33 100644
--- a/src/gpu/GrPipelineBuilder.cpp
+++ b/src/gpu/GrPipelineBuilder.cpp
@@ -15,21 +15,21 @@
 #include "effects/GrPorterDuffXferProcessor.h"
 #include "ops/GrOp.h"
 
-GrPipelineBuilder::GrPipelineBuilder(const GrPaint& paint, GrAAType aaType)
+GrPipelineBuilder::GrPipelineBuilder(GrPaint&& paint, GrAAType aaType)
         : fFlags(0x0)
         , fUserStencilSettings(&GrUserStencilSettings::kUnused)
         , fDrawFace(GrDrawFace::kBoth) {
     SkDEBUGCODE(fBlockEffectRemovalCnt = 0;)
 
     for (int i = 0; i < paint.numColorFragmentProcessors(); ++i) {
-        fColorFragmentProcessors.emplace_back(SkRef(paint.getColorFragmentProcessor(i)));
+        fColorFragmentProcessors.emplace_back(paint.fColorFragmentProcessors[i].release());
     }
 
     for (int i = 0; i < paint.numCoverageFragmentProcessors(); ++i) {
-        fCoverageFragmentProcessors.emplace_back(SkRef(paint.getCoverageFragmentProcessor(i)));
+        fCoverageFragmentProcessors.emplace_back(paint.fCoverageFragmentProcessors[i].release());
     }
 
-    fXPFactory.reset(SkSafeRef(paint.getXPFactory()));
+    fXPFactory = paint.getXPFactory();
 
     this->setState(GrPipelineBuilder::kHWAntialias_Flag, GrAATypeIsHW(aaType));
     this->setState(GrPipelineBuilder::kDisableOutputConversionToSRGB_Flag,
diff --git a/src/gpu/GrPipelineBuilder.h b/src/gpu/GrPipelineBuilder.h
index 1bc9002..15af681 100644
--- a/src/gpu/GrPipelineBuilder.h
+++ b/src/gpu/GrPipelineBuilder.h
@@ -29,14 +29,13 @@
 
 class GrPipelineBuilder : public SkNoncopyable {
 public:
-//    GrPipelineBuilder();
     /**
      * Initializes the GrPipelineBuilder based on a GrPaint and MSAA availability. Note
      * that GrPipelineBuilder encompasses more than GrPaint. Aspects of GrPipelineBuilder that have
      * no GrPaint equivalents are set to default values with the exception of vertex attribute state
      * which is unmodified by this function and clipping which will be enabled.
      */
-    GrPipelineBuilder(const GrPaint& paint, GrAAType aaType);
+    GrPipelineBuilder(GrPaint&&, GrAAType);
 
     virtual ~GrPipelineBuilder();
 
@@ -145,21 +144,15 @@
      * Installs a GrXPFactory. This object controls how src color, fractional pixel coverage,
      * and the dst color are blended.
      */
-    void setXPFactory(sk_sp<GrXPFactory> xpFactory) {
-        fXPFactory = std::move(xpFactory);
-    }
+    void setXPFactory(const GrXPFactory* xpFactory) { fXPFactory = xpFactory; }
 
     /**
      * Sets a GrXPFactory that disables color writes to the destination. This is useful when
      * rendering to the stencil buffer.
      */
-    void setDisableColorXPFactory() {
-        fXPFactory = GrDisableColorXPFactory::Make();
-    }
+    void setDisableColorXPFactory() { fXPFactory = GrDisableColorXPFactory::Get(); }
 
-    const GrXPFactory* getXPFactory() const {
-        return fXPFactory.get();
-    }
+    const GrXPFactory* getXPFactory() const { return fXPFactory; }
 
     /**
      * Checks whether the xp will need destination in a texture to correctly blend.
@@ -304,7 +297,7 @@
     uint32_t                                fFlags;
     const GrUserStencilSettings*            fUserStencilSettings;
     GrDrawFace                              fDrawFace;
-    mutable sk_sp<GrXPFactory>              fXPFactory;
+    const GrXPFactory*                      fXPFactory;
     FragmentProcessorArray                  fColorFragmentProcessors;
     FragmentProcessorArray                  fCoverageFragmentProcessors;
 
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index ed2b2e3..f1af14d 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -56,13 +56,6 @@
  */
 class GrPipelineOptimizations {
 public:
-    /** Does the pipeline require the GrPrimitiveProcessor's color? */
-    bool readsColor() const { return SkToBool(kReadsColor_Flag & fFlags); }
-
-    /** Does the pipeline require the GrPrimitiveProcessor's coverage? */
-    bool readsCoverage() const { return
-        SkToBool(kReadsCoverage_Flag & fFlags); }
-
     /** Does the pipeline require access to (implicit or explicit) local coordinates? */
     bool readsLocalCoords() const {
         return SkToBool(kReadsLocalCoords_Flag & fFlags);
@@ -78,7 +71,6 @@
         so get the color)? */
     bool getOverrideColorIfSet(GrColor* overrideColor) const {
         if (SkToBool(kUseOverrideColor_Flag & fFlags)) {
-            SkASSERT(SkToBool(kReadsColor_Flag & fFlags));
             if (overrideColor) {
                 *overrideColor = fOverrideColor;
             }
@@ -102,24 +94,18 @@
 
 private:
     enum {
-        // If this is not set the primitive processor need not produce a color output
-        kReadsColor_Flag                = 0x1,
-
-        // If this is not set the primitive processor need not produce a coverage output
-        kReadsCoverage_Flag             = 0x2,
-
         // If this is not set the primitive processor need not produce local coordinates
-        kReadsLocalCoords_Flag          = 0x4,
+        kReadsLocalCoords_Flag = 0x1,
 
         // If this flag is set then the primitive processor may produce color*coverage as
         // its color output (and not output a separate coverage).
-        kCanTweakAlphaForCoverage_Flag  = 0x8,
+        kCanTweakAlphaForCoverage_Flag = 0x2,
 
         // If this flag is set the GrPrimitiveProcessor must produce fOverrideColor as its
         // output color. If not set fOverrideColor is to be ignored.
-        kUseOverrideColor_Flag          = 0x10,
+        kUseOverrideColor_Flag = 0x4,
 
-        kWillColorBlendWithDst_Flag     = 0x20,
+        kWillColorBlendWithDst_Flag = 0x8,
     };
 
     uint32_t    fFlags;
diff --git a/src/gpu/GrProcessor.cpp b/src/gpu/GrProcessor.cpp
index f4f7ccb..f4bead2 100644
--- a/src/gpu/GrProcessor.cpp
+++ b/src/gpu/GrProcessor.cpp
@@ -32,19 +32,17 @@
 }
 
 template<>
-SkTArray<GrProcessorTestFactory<GrXPFactory>*, true>*
-GrProcessorTestFactory<GrXPFactory>::GetFactories() {
-    static SkTArray<GrProcessorTestFactory<GrXPFactory>*, true> gFactories;
-    return &gFactories;
-}
-
-template<>
 SkTArray<GrProcessorTestFactory<GrGeometryProcessor>*, true>*
 GrProcessorTestFactory<GrGeometryProcessor>::GetFactories() {
     static SkTArray<GrProcessorTestFactory<GrGeometryProcessor>*, true> gFactories;
     return &gFactories;
 }
 
+SkTArray<GrXPFactoryTestFactory*, true>* GrXPFactoryTestFactory::GetFactories() {
+    static SkTArray<GrXPFactoryTestFactory*, true> gFactories;
+    return &gFactories;
+}
+
 /*
  * To ensure we always have successful static initialization, before creating from the factories
  * we verify the count is as expected.  If a new factory is added, then these numbers must be
@@ -52,7 +50,7 @@
  */
 static const int kFPFactoryCount = 40;
 static const int kGPFactoryCount = 14;
-static const int kXPFactoryCount = 5;
+static const int kXPFactoryCount = 4;
 
 template<>
 void GrProcessorTestFactory<GrFragmentProcessor>::VerifyFactoryCount() {
@@ -72,8 +70,7 @@
     }
 }
 
-template<>
-void GrProcessorTestFactory<GrXPFactory>::VerifyFactoryCount() {
+void GrXPFactoryTestFactory::VerifyFactoryCount() {
     if (kXPFactoryCount != GetFactories()->count()) {
         SkDebugf("\nExpected %d xp factory factories, found %d.\n",
                  kXPFactoryCount, GetFactories()->count());
@@ -117,17 +114,50 @@
 
 void GrProcessor::addTextureSampler(const TextureSampler* access) {
     fTextureSamplers.push_back(access);
-    this->addGpuResource(access->programTexture());
 }
 
 void GrProcessor::addBufferAccess(const BufferAccess* access) {
     fBufferAccesses.push_back(access);
-    this->addGpuResource(access->programBuffer());
 }
 
 void GrProcessor::addImageStorageAccess(const ImageStorageAccess* access) {
     fImageStorageAccesses.push_back(access);
-    this->addGpuResource(access->programTexture());
+}
+
+void GrProcessor::addPendingIOs() const {
+    for (const auto& sampler : fTextureSamplers) {
+        sampler->programTexture()->markPendingIO();
+    }
+    for (const auto& buffer : fBufferAccesses) {
+        buffer->programBuffer()->markPendingIO();
+    }
+    for (const auto& imageStorage : fImageStorageAccesses) {
+        imageStorage->programTexture()->markPendingIO();
+    }
+}
+
+void GrProcessor::removeRefs() const {
+    for (const auto& sampler : fTextureSamplers) {
+        sampler->programTexture()->removeRef();
+    }
+    for (const auto& buffer : fBufferAccesses) {
+        buffer->programBuffer()->removeRef();
+    }
+    for (const auto& imageStorage : fImageStorageAccesses) {
+        imageStorage->programTexture()->removeRef();
+    }
+}
+
+void GrProcessor::pendingIOComplete() const {
+    for (const auto& sampler : fTextureSamplers) {
+        sampler->programTexture()->pendingIOComplete();
+    }
+    for (const auto& buffer : fBufferAccesses) {
+        buffer->programBuffer()->pendingIOComplete();
+    }
+    for (const auto& imageStorage : fImageStorageAccesses) {
+        imageStorage->programTexture()->pendingIOComplete();
+    }
 }
 
 void* GrProcessor::operator new(size_t size) {
@@ -229,8 +259,3 @@
             break;
     }
 }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-// Initial static variable from GrXPFactory
-int32_t GrXPFactory::gCurrXPFClassID = GrXPFactory::kIllegalXPFClassID;
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index c6a1bcf..87a4a27 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -214,8 +214,6 @@
 
     header->fOutputSwizzle = shaderCaps.configOutputSwizzle(rt->config()).asKey();
 
-    header->fIgnoresCoverage = pipeline.ignoresCoverage() ? 1 : 0;
-
     header->fSnapVerticesToPixelCenters = pipeline.snapVerticesToPixelCenters();
     header->fColorFragmentProcessorCnt = pipeline.numColorFragmentProcessors();
     header->fCoverageFragmentProcessorCnt = pipeline.numCoverageFragmentProcessors();
diff --git a/src/gpu/GrProgramDesc.h b/src/gpu/GrProgramDesc.h
index 5bb43f9..2a26f2d 100644
--- a/src/gpu/GrProgramDesc.h
+++ b/src/gpu/GrProgramDesc.h
@@ -103,10 +103,9 @@
         uint8_t                     fCoverageFragmentProcessorCnt : 4;
         // Set to uniquely identify the rt's origin, or 0 if the shader does not require this info.
         uint8_t                     fSurfaceOriginKey : 2;
-        uint8_t                     fIgnoresCoverage : 1;
         uint8_t                     fSnapVerticesToPixelCenters : 1;
         uint8_t                     fHasPointSize : 1;
-        uint8_t                     fPad : 3;
+        uint8_t                     fPad : 4;
     };
     GR_STATIC_ASSERT(sizeof(KeyHeader) == 4);
 
diff --git a/src/gpu/GrProgramElement.cpp b/src/gpu/GrProgramElement.cpp
deleted file mode 100644
index f1f3f41..0000000
--- a/src/gpu/GrProgramElement.cpp
+++ /dev/null
@@ -1,37 +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 "GrProgramElement.h"
-#include "GrGpuResourceRef.h"
-#include "SkAtomics.h"
-
-uint32_t GrProgramElement::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;
-}
-
-void GrProgramElement::addPendingIOs() const {
-    for (int i = 0; i < fGpuResources.count(); ++i) {
-        fGpuResources[i]->markPendingIO();
-    }
-}
-
-void GrProgramElement::removeRefs() const {
-    for (int i = 0; i < fGpuResources.count(); ++i) {
-        fGpuResources[i]->removeRef();
-    }
-}
-
-void GrProgramElement::pendingIOComplete() const {
-    for (int i = 0; i < fGpuResources.count(); ++i) {
-        fGpuResources[i]->pendingIOComplete();
-    }
-}
diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp
index 9802f87..4dd81c2 100644
--- a/src/gpu/GrReducedClip.cpp
+++ b/src/gpu/GrReducedClip.cpp
@@ -558,19 +558,18 @@
 }
 
 static void draw_element(GrRenderTargetContext* rtc,
-                         const GrClip& clip, // TODO: can this just always be WideOpen?
-                         const GrPaint &paint,
+                         const GrClip& clip,  // TODO: can this just always be WideOpen?
+                         GrPaint&& paint,
                          GrAA aa,
                          const SkMatrix& viewMatrix,
                          const SkClipStack::Element* element) {
-
     // TODO: Draw rrects directly here.
     switch (element->getType()) {
         case Element::kEmpty_Type:
             SkDEBUGFAIL("Should never get here with an empty element.");
             break;
         case Element::kRect_Type:
-            rtc->drawRect(clip, paint, aa, viewMatrix, element->getRect());
+            rtc->drawRect(clip, std::move(paint), aa, viewMatrix, element->getRect());
             break;
         default: {
             SkPath path;
@@ -579,7 +578,7 @@
                 path.toggleInverseFillType();
             }
 
-            rtc->drawPath(clip, paint, aa, viewMatrix, path, GrStyle::SimpleFill());
+            rtc->drawPath(clip, std::move(paint), aa, viewMatrix, path, GrStyle::SimpleFill());
             break;
         }
     }
@@ -645,7 +644,7 @@
             GrPaint paint;
             paint.setCoverageSetOpXPFactory(op, false);
 
-            draw_element(rtc, clip, paint, aa, translate, element);
+            draw_element(rtc, clip, std::move(paint), aa, translate, element);
         }
     }
 
@@ -780,18 +779,17 @@
                     GrShape shape(clipPath, GrStyle::SimpleFill());
                     if (canRenderDirectToStencil) {
                         GrPaint paint;
-                        paint.setXPFactory(GrDisableColorXPFactory::Make());
+                        paint.setXPFactory(GrDisableColorXPFactory::Get());
 
-                        GrPathRenderer::DrawPathArgs args;
-                        args.fResourceProvider = context->resourceProvider();
-                        args.fPaint = &paint;
-                        args.fUserStencilSettings = &kDrawToStencil;
-                        args.fRenderTargetContext = renderTargetContext;
-                        args.fClip = &stencilClip.fixedClip();
-                        args.fViewMatrix = &viewMatrix;
-                        args.fShape = &shape;
-                        args.fAAType = aaType;
-                        args.fGammaCorrect = false;
+                        GrPathRenderer::DrawPathArgs args{context->resourceProvider(),
+                                                          std::move(paint),
+                                                          &kDrawToStencil,
+                                                          renderTargetContext,
+                                                          &stencilClip.fixedClip(),
+                                                          &viewMatrix,
+                                                          &shape,
+                                                          aaType,
+                                                          false};
                         pr->drawPath(args);
                     } else {
                         GrPathRenderer::StencilPathArgs args;
@@ -817,17 +815,16 @@
                 } else {
                     GrShape shape(clipPath, GrStyle::SimpleFill());
                     GrPaint paint;
-                    paint.setXPFactory(GrDisableColorXPFactory::Make());
-                    GrPathRenderer::DrawPathArgs args;
-                    args.fResourceProvider = context->resourceProvider();
-                    args.fPaint = &paint;
-                    args.fUserStencilSettings = *pass;
-                    args.fRenderTargetContext = renderTargetContext;
-                    args.fClip = &stencilClip;
-                    args.fViewMatrix = &viewMatrix;
-                    args.fShape = &shape;
-                    args.fAAType = aaType;
-                    args.fGammaCorrect = false;
+                    paint.setXPFactory(GrDisableColorXPFactory::Get());
+                    GrPathRenderer::DrawPathArgs args{context->resourceProvider(),
+                                                      std::move(paint),
+                                                      *pass,
+                                                      renderTargetContext,
+                                                      &stencilClip,
+                                                      &viewMatrix,
+                                                      &shape,
+                                                      aaType,
+                                                      false};
                     pr->drawPath(args);
                 }
             } else {
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 10881de..968663c 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -148,7 +148,7 @@
         return false;
     }
 
-    // TODO: this needs to be fixed up since it ends the deferrable of the GrRenderTarget
+    // TODO: This needs to be fixed up since it ends the deferral of the GrRenderTarget.
     sk_sp<GrRenderTarget> rt(
                         sk_ref_sp(fRenderTargetProxy->instantiate(fContext->textureProvider())));
     if (!rt) {
@@ -158,37 +158,34 @@
     return this->getOpList()->copySurface(rt.get(), src.get(), srcRect, dstPoint);
 }
 
-void GrRenderTargetContext::drawText(const GrClip& clip, const GrPaint& grPaint,
-                                     const SkPaint& skPaint,
-                                     const SkMatrix& viewMatrix,
-                                     const char text[], size_t byteLength,
-                                     SkScalar x, SkScalar y, const SkIRect& clipBounds) {
+void GrRenderTargetContext::drawText(const GrClip& clip, GrPaint&& grPaint, const SkPaint& skPaint,
+                                     const SkMatrix& viewMatrix, const char text[],
+                                     size_t byteLength, SkScalar x, SkScalar y,
+                                     const SkIRect& clipBounds) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
     GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrRenderTargetContext::drawText");
 
     GrAtlasTextContext* atlasTextContext = fDrawingManager->getAtlasTextContext();
-    atlasTextContext->drawText(fContext, this, clip, grPaint, skPaint, viewMatrix, fSurfaceProps,
-                               text, byteLength, x, y, clipBounds);
+    atlasTextContext->drawText(fContext, this, clip, std::move(grPaint), skPaint, viewMatrix,
+                               fSurfaceProps, text, byteLength, x, y, clipBounds);
 }
 
-void GrRenderTargetContext::drawPosText(const GrClip& clip, const GrPaint& grPaint,
-                                        const SkPaint& skPaint,
-                                        const SkMatrix& viewMatrix,
-                                        const char text[], size_t byteLength,
-                                        const SkScalar pos[], int scalarsPerPosition,
-                                        const SkPoint& offset, const SkIRect& clipBounds) {
+void GrRenderTargetContext::drawPosText(const GrClip& clip, GrPaint&& grPaint,
+                                        const SkPaint& skPaint, const SkMatrix& viewMatrix,
+                                        const char text[], size_t byteLength, const SkScalar pos[],
+                                        int scalarsPerPosition, const SkPoint& offset,
+                                        const SkIRect& clipBounds) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
     GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrRenderTargetContext::drawPosText");
 
     GrAtlasTextContext* atlasTextContext = fDrawingManager->getAtlasTextContext();
-    atlasTextContext->drawPosText(fContext, this, clip, grPaint, skPaint, viewMatrix,
-                                  fSurfaceProps, text, byteLength, pos, scalarsPerPosition,
-                                  offset, clipBounds);
-
+    atlasTextContext->drawPosText(fContext, this, clip, std::move(grPaint), skPaint, viewMatrix,
+                                  fSurfaceProps, text, byteLength, pos, scalarsPerPosition, offset,
+                                  clipBounds);
 }
 
 void GrRenderTargetContext::drawTextBlob(const GrClip& clip, const SkPaint& skPaint,
@@ -213,14 +210,14 @@
 
     AutoCheckFlush acf(fDrawingManager);
 
-    // TODO: this needs to be fixed up since it ends the deferrable of the GrRenderTarget
+    // TODO: This needs to be fixed up since it ends the deferral of the GrRenderTarget.
     sk_sp<GrRenderTarget> rt(
                         sk_ref_sp(fRenderTargetProxy->instantiate(fContext->textureProvider())));
     if (!rt) {
         return;
     }
 
-    this->getOpList()->discard(rt.get());
+    this->getOpList()->discard(this);
 }
 
 void GrRenderTargetContext::clear(const SkIRect* rect,
@@ -268,13 +265,13 @@
         // target before the target is read.
         GrPaint paint;
         paint.setColor4f(GrColor4f::FromGrColor(color));
-        paint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+        paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
 
         // We don't call drawRect() here to avoid the cropping to the, possibly smaller,
         // RenderTargetProxy bounds
-        fRenderTargetContext->drawNonAAFilledRect(GrNoClip(), paint, SkMatrix::I(),
-                                                  SkRect::Make(rtRect),
-                                                  nullptr, nullptr, nullptr, GrAAType::kNone);
+        fRenderTargetContext->drawNonAAFilledRect(GrNoClip(), std::move(paint), SkMatrix::I(),
+                                                  SkRect::Make(rtRect), nullptr, nullptr, nullptr,
+                                                  GrAAType::kNone);
 
     } else {
         if (!fRenderTargetContext->accessRenderTarget()) {
@@ -284,12 +281,12 @@
         // This path doesn't handle coalescing of full screen clears b.c. it
         // has to clear the entire render target - not just the content area.
         // It could be done but will take more finagling.
-        sk_sp<GrOp> op(GrClearOp::Make(rtRect, color, fRenderTargetContext->accessRenderTarget(),
-                                       !clearRect));
+        std::unique_ptr<GrOp> op(GrClearOp::Make(
+                rtRect, color, fRenderTargetContext->accessRenderTarget(), !clearRect));
         if (!op) {
             return;
         }
-        fRenderTargetContext->getOpList()->addOp(std::move(op));
+        fRenderTargetContext->getOpList()->addOp(std::move(op), fRenderTargetContext);
     }
 }
 
@@ -329,27 +326,27 @@
 
         GrPaint paint;
         paint.setColor4f(GrColor4f::FromGrColor(color));
-        paint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+        paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
 
-        this->drawRect(clip, paint, GrAA::kNo, SkMatrix::I(), SkRect::Make(clearRect));
+        this->drawRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), SkRect::Make(clearRect));
     } else if (isFull) {
         if (this->accessRenderTarget()) {
-            this->getOpList()->fullClear(this->accessRenderTarget(), color);
+            this->getOpList()->fullClear(this, color);
         }
     } else {
         if (!this->accessRenderTarget()) {
             return;
         }
-        sk_sp<GrOp> op(GrClearOp::Make(clip, color, this->accessRenderTarget()));
+        std::unique_ptr<GrOp> op(GrClearOp::Make(clip, color, this->accessRenderTarget()));
         if (!op) {
             return;
         }
-        this->getOpList()->addOp(std::move(op));
+        this->getOpList()->addOp(std::move(op), this);
     }
 }
 
 void GrRenderTargetContext::drawPaint(const GrClip& clip,
-                                      const GrPaint& paint,
+                                      GrPaint&& paint,
                                       const SkMatrix& viewMatrix) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
@@ -369,7 +366,8 @@
     // because they may depend on having correct local coords and this path draws in device space
     // without a local matrix.
     if (!paint.numTotalFragmentProcessors() && clip.isRRect(r, &rrect, &aa) && !rrect.isRect()) {
-        this->drawRRect(GrNoClip(), paint, aa, SkMatrix::I(), rrect, GrStyle::SimpleFill());
+        this->drawRRect(GrNoClip(), std::move(paint), aa, SkMatrix::I(), rrect,
+                        GrStyle::SimpleFill());
         return;
     }
 
@@ -384,7 +382,7 @@
             SkDebugf("Could not invert matrix\n");
             return;
         }
-        this->drawRect(clip, paint, GrAA::kNo, viewMatrix, r);
+        this->drawRect(clip, std::move(paint), GrAA::kNo, viewMatrix, r);
     } else {
         SkMatrix localMatrix;
         if (!viewMatrix.invert(&localMatrix)) {
@@ -394,7 +392,7 @@
 
         AutoCheckFlush acf(fDrawingManager);
 
-        this->drawNonAAFilledRect(clip, paint, SkMatrix::I(), r, nullptr, &localMatrix,
+        this->drawNonAAFilledRect(clip, std::move(paint), SkMatrix::I(), r, nullptr, &localMatrix,
                                   nullptr, GrAAType::kNone);
     }
 }
@@ -454,7 +452,7 @@
 }
 
 bool GrRenderTargetContext::drawFilledRect(const GrClip& clip,
-                                           const GrPaint& paint,
+                                           GrPaint&& paint,
                                            GrAA aa,
                                            const SkMatrix& viewMatrix,
                                            const SkRect& rect,
@@ -464,7 +462,7 @@
         return true;
     }
 
-    sk_sp<GrDrawOp> op;
+    std::unique_ptr<GrDrawOp> op;
     GrAAType aaType;
 
     if (GrCaps::InstancedSupport::kNone != fContext->caps()->instancedSupport()) {
@@ -472,7 +470,7 @@
         op = ir->recordRect(croppedRect, viewMatrix, paint.getColor(), aa, fInstancedPipelineInfo,
                             &aaType);
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             if (ss) {
                 pipelineBuilder.setUserStencil(ss);
             }
@@ -489,7 +487,7 @@
 
             op = GrRectOpFactory::MakeAAFill(paint, viewMatrix, rect, croppedRect, devBoundRect);
             if (op) {
-                GrPipelineBuilder pipelineBuilder(paint, aaType);
+                GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
                 if (ss) {
                     pipelineBuilder.setUserStencil(ss);
                 }
@@ -498,8 +496,8 @@
             }
         }
     } else {
-        this->drawNonAAFilledRect(clip, paint, viewMatrix, croppedRect, nullptr, nullptr, ss,
-                                  aaType);
+        this->drawNonAAFilledRect(clip, std::move(paint), viewMatrix, croppedRect, nullptr, nullptr,
+                                  ss, aaType);
         return true;
     }
 
@@ -507,7 +505,7 @@
 }
 
 void GrRenderTargetContext::drawRect(const GrClip& clip,
-                                     const GrPaint& paint,
+                                     GrPaint&& paint,
                                      GrAA aa,
                                      const SkMatrix& viewMatrix,
                                      const SkRect& rect,
@@ -555,7 +553,7 @@
             }
         }
 
-        if (this->drawFilledRect(clip, paint, aa, viewMatrix, rect, nullptr)) {
+        if (this->drawFilledRect(clip, std::move(paint), aa, viewMatrix, rect, nullptr)) {
             return;
         }
     } else if (stroke.getStyle() == SkStrokeRec::kStroke_Style ||
@@ -566,25 +564,26 @@
             // TODO: Move these stroke->fill fallbacks to GrShape?
             switch (stroke.getJoin()) {
                 case SkPaint::kMiter_Join:
-                    this->drawRect(clip, paint, aa, viewMatrix,
-                                   {rect.fLeft - r, rect.fTop - r,
-                                    rect.fRight + r, rect.fBottom + r},
-                                   &GrStyle::SimpleFill());
+                    this->drawRect(
+                            clip, std::move(paint), aa, viewMatrix,
+                            {rect.fLeft - r, rect.fTop - r, rect.fRight + r, rect.fBottom + r},
+                            &GrStyle::SimpleFill());
                     return;
                 case SkPaint::kRound_Join:
                     // Raster draws nothing when both dimensions are empty.
                     if (rect.width() || rect.height()){
                         SkRRect rrect = SkRRect::MakeRectXY(rect.makeOutset(r, r), r, r);
-                        this->drawRRect(clip, paint, aa, viewMatrix, rrect, GrStyle::SimpleFill());
+                        this->drawRRect(clip, std::move(paint), aa, viewMatrix, rrect,
+                                        GrStyle::SimpleFill());
                         return;
                     }
                 case SkPaint::kBevel_Join:
                     if (!rect.width()) {
-                        this->drawRect(clip, paint, aa, viewMatrix,
+                        this->drawRect(clip, std::move(paint), aa, viewMatrix,
                                        {rect.fLeft - r, rect.fTop, rect.fRight + r, rect.fBottom},
                                        &GrStyle::SimpleFill());
                     } else {
-                        this->drawRect(clip, paint, aa, viewMatrix,
+                        this->drawRect(clip, std::move(paint), aa, viewMatrix,
                                        {rect.fLeft, rect.fTop - r, rect.fRight, rect.fBottom + r},
                                        &GrStyle::SimpleFill());
                     }
@@ -593,7 +592,7 @@
         }
 
         bool snapToPixelCenters = false;
-        sk_sp<GrDrawOp> op;
+        std::unique_ptr<GrDrawOp> op;
 
         GrColor color = paint.getColor();
         GrAAType aaType = this->decideAAType(aa);
@@ -613,7 +612,7 @@
         }
 
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
 
             if (snapToPixelCenters) {
                 pipelineBuilder.setState(GrPipelineBuilder::kSnapVerticesToPixelCenters_Flag,
@@ -628,7 +627,7 @@
     SkPath path;
     path.setIsVolatile(true);
     path.addRect(rect);
-    this->internalDrawPath(clip, paint, aa, viewMatrix, path, *style);
+    this->internalDrawPath(clip, std::move(paint), aa, viewMatrix, path, *style);
 }
 
 int GrRenderTargetContextPriv::maxWindowRectangles() const {
@@ -644,11 +643,12 @@
                               "GrRenderTargetContextPriv::clearStencilClip");
 
     AutoCheckFlush acf(fRenderTargetContext->fDrawingManager);
+    // TODO: This needs to be fixed up since it ends the deferral of the GrRenderTarget.
     if (!fRenderTargetContext->accessRenderTarget()) {
         return;
     }
     fRenderTargetContext->getOpList()->clearStencilClip(clip, insideStencilMask,
-                                                        fRenderTargetContext->accessRenderTarget());
+                                                        fRenderTargetContext);
 }
 
 void GrRenderTargetContextPriv::stencilPath(const GrClip& clip,
@@ -674,10 +674,10 @@
     AutoCheckFlush acf(fRenderTargetContext->fDrawingManager);
 
     GrPaint paint;
-    paint.setXPFactory(GrDisableColorXPFactory::Make());
+    paint.setXPFactory(GrDisableColorXPFactory::Get());
 
-    fRenderTargetContext->drawNonAAFilledRect(clip, paint, viewMatrix, rect, nullptr, nullptr, ss,
-                                              aaType);
+    fRenderTargetContext->drawNonAAFilledRect(clip, std::move(paint), viewMatrix, rect, nullptr,
+                                              nullptr, ss, aaType);
 }
 
 bool GrRenderTargetContextPriv::drawAndStencilRect(const GrClip& clip,
@@ -698,7 +698,7 @@
     GrPaint paint;
     paint.setCoverageSetOpXPFactory(op, invert);
 
-    if (fRenderTargetContext->drawFilledRect(clip, paint, aa, viewMatrix, rect, ss)) {
+    if (fRenderTargetContext->drawFilledRect(clip, std::move(paint), aa, viewMatrix, rect, ss)) {
         return true;
     }
     SkPath path;
@@ -708,7 +708,7 @@
 }
 
 void GrRenderTargetContext::fillRectToRect(const GrClip& clip,
-                                           const GrPaint& paint,
+                                           GrPaint&& paint,
                                            GrAA aa,
                                            const SkMatrix& viewMatrix,
                                            const SkRect& rectToDraw,
@@ -730,10 +730,11 @@
 
     if (GrCaps::InstancedSupport::kNone != fContext->caps()->instancedSupport()) {
         InstancedRendering* ir = this->getOpList()->instancedRendering();
-        sk_sp<GrDrawOp> op(ir->recordRect(croppedRect, viewMatrix, paint.getColor(),
-                                          croppedLocalRect, aa, fInstancedPipelineInfo, &aaType));
+        std::unique_ptr<GrDrawOp> op(ir->recordRect(croppedRect, viewMatrix, paint.getColor(),
+                                                    croppedLocalRect, aa, fInstancedPipelineInfo,
+                                                    &aaType));
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
             return;
         }
@@ -741,15 +742,15 @@
 
     aaType = this->decideAAType(aa);
     if (GrAAType::kCoverage != aaType) {
-        this->drawNonAAFilledRect(clip, paint, viewMatrix, croppedRect, &croppedLocalRect, nullptr,
-                                  nullptr, aaType);
+        this->drawNonAAFilledRect(clip, std::move(paint), viewMatrix, croppedRect,
+                                  &croppedLocalRect, nullptr, nullptr, aaType);
         return;
     }
 
     if (view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
-        sk_sp<GrDrawOp> op = GrAAFillRectOp::MakeWithLocalRect(paint.getColor(), viewMatrix,
-                                                               croppedRect, croppedLocalRect);
-        GrPipelineBuilder pipelineBuilder(paint, aaType);
+        std::unique_ptr<GrDrawOp> op = GrAAFillRectOp::MakeWithLocalRect(
+                paint.getColor(), viewMatrix, croppedRect, croppedLocalRect);
+        GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
         this->addDrawOp(pipelineBuilder, clip, std::move(op));
         return;
     }
@@ -764,11 +765,11 @@
     SkPath path;
     path.setIsVolatile(true);
     path.addRect(localRect);
-    this->internalDrawPath(clip, paint, aa, viewAndUnLocalMatrix, path, GrStyle());
+    this->internalDrawPath(clip, std::move(paint), aa, viewAndUnLocalMatrix, path, GrStyle());
 }
 
 void GrRenderTargetContext::fillRectWithLocalMatrix(const GrClip& clip,
-                                                    const GrPaint& paint,
+                                                    GrPaint&& paint,
                                                     GrAA aa,
                                                     const SkMatrix& viewMatrix,
                                                     const SkRect& rectToDraw,
@@ -788,10 +789,11 @@
 
     if (GrCaps::InstancedSupport::kNone != fContext->caps()->instancedSupport()) {
         InstancedRendering* ir = this->getOpList()->instancedRendering();
-        sk_sp<GrDrawOp> op(ir->recordRect(croppedRect, viewMatrix, paint.getColor(), localMatrix,
-                                          aa, fInstancedPipelineInfo, &aaType));
+        std::unique_ptr<GrDrawOp> op(ir->recordRect(croppedRect, viewMatrix, paint.getColor(),
+                                                    localMatrix, aa, fInstancedPipelineInfo,
+                                                    &aaType));
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
             return;
         }
@@ -799,15 +801,15 @@
 
     aaType = this->decideAAType(aa);
     if (GrAAType::kCoverage != aaType) {
-        this->drawNonAAFilledRect(clip, paint, viewMatrix, croppedRect, nullptr, &localMatrix,
-                                  nullptr, aaType);
+        this->drawNonAAFilledRect(clip, std::move(paint), viewMatrix, croppedRect, nullptr,
+                                  &localMatrix, nullptr, aaType);
         return;
     }
 
     if (view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
-        sk_sp<GrDrawOp> op =
+        std::unique_ptr<GrDrawOp> op =
                 GrAAFillRectOp::Make(paint.getColor(), viewMatrix, localMatrix, croppedRect);
-        GrPipelineBuilder pipelineBuilder(paint, aaType);
+        GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
         this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
         return;
     }
@@ -823,11 +825,11 @@
     path.setIsVolatile(true);
     path.addRect(rectToDraw);
     path.transform(localMatrix);
-    this->internalDrawPath(clip, paint, aa, viewAndUnLocalMatrix, path, GrStyle());
+    this->internalDrawPath(clip, std::move(paint), aa, viewAndUnLocalMatrix, path, GrStyle());
 }
 
 void GrRenderTargetContext::drawVertices(const GrClip& clip,
-                                         const GrPaint& paint,
+                                         GrPaint&& paint,
                                          const SkMatrix& viewMatrix,
                                          GrPrimitiveType primitiveType,
                                          int vertexCount,
@@ -852,18 +854,18 @@
 
     viewMatrix.mapRect(&bounds);
 
-    sk_sp<GrDrawOp> op =
+    std::unique_ptr<GrDrawOp> op =
             GrDrawVerticesOp::Make(paint.getColor(), primitiveType, viewMatrix, positions,
                                    vertexCount, indices, indexCount, colors, texCoords, bounds);
 
-    GrPipelineBuilder pipelineBuilder(paint, GrAAType::kNone);
+    GrPipelineBuilder pipelineBuilder(std::move(paint), GrAAType::kNone);
     this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 void GrRenderTargetContext::drawAtlas(const GrClip& clip,
-                                      const GrPaint& paint,
+                                      GrPaint&& paint,
                                       const SkMatrix& viewMatrix,
                                       int spriteCount,
                                       const SkRSXform xform[],
@@ -876,16 +878,16 @@
 
     AutoCheckFlush acf(fDrawingManager);
 
-    sk_sp<GrDrawOp> op =
+    std::unique_ptr<GrDrawOp> op =
             GrDrawAtlasOp::Make(paint.getColor(), viewMatrix, spriteCount, xform, texRect, colors);
-    GrPipelineBuilder pipelineBuilder(paint, GrAAType::kNone);
+    GrPipelineBuilder pipelineBuilder(std::move(paint), GrAAType::kNone);
     this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 void GrRenderTargetContext::drawRRect(const GrClip& origClip,
-                                      const GrPaint& paint,
+                                      GrPaint&& paint,
                                       GrAA aa,
                                       const SkMatrix& viewMatrix,
                                       const SkRRect& rrect,
@@ -920,10 +922,10 @@
     if (GrCaps::InstancedSupport::kNone != fContext->caps()->instancedSupport() &&
         stroke.isFillStyle()) {
         InstancedRendering* ir = this->getOpList()->instancedRendering();
-        sk_sp<GrDrawOp> op(ir->recordRRect(rrect, viewMatrix, paint.getColor(), aa,
-                                           fInstancedPipelineInfo, &aaType));
+        std::unique_ptr<GrDrawOp> op(ir->recordRRect(rrect, viewMatrix, paint.getColor(), aa,
+                                                     fInstancedPipelineInfo, &aaType));
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             this->getOpList()->addDrawOp(pipelineBuilder, this, *clip, std::move(op));
             return;
         }
@@ -932,14 +934,14 @@
     aaType = this->decideAAType(aa);
     if (GrAAType::kCoverage == aaType) {
         const GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
-        sk_sp<GrDrawOp> op = GrOvalOpFactory::MakeRRectOp(paint.getColor(),
-                                                          paint.usesDistanceVectorField(),
-                                                          viewMatrix,
-                                                          rrect,
-                                                          stroke,
-                                                          shaderCaps);
+        std::unique_ptr<GrDrawOp> op = GrOvalOpFactory::MakeRRectOp(paint.getColor(),
+                                                                    paint.usesDistanceVectorField(),
+                                                                    viewMatrix,
+                                                                    rrect,
+                                                                    stroke,
+                                                                    shaderCaps);
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             this->getOpList()->addDrawOp(pipelineBuilder, this, *clip, std::move(op));
             return;
         }
@@ -948,13 +950,13 @@
     SkPath path;
     path.setIsVolatile(true);
     path.addRRect(rrect);
-    this->internalDrawPath(*clip, paint, aa, viewMatrix, path, style);
+    this->internalDrawPath(*clip, std::move(paint), aa, viewMatrix, path, style);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 void GrRenderTargetContext::drawShadowRRect(const GrClip& clip,
-                                            const GrPaint& paint,
+                                            GrPaint&& paint,
                                             const SkMatrix& viewMatrix,
                                             const SkRRect& rrect,
                                             SkScalar blurRadius,
@@ -974,10 +976,10 @@
     // TODO: add instancing support?
 
     const GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
-    sk_sp<GrDrawOp> op = GrShadowRRectOp::Make(paint.getColor(), viewMatrix, rrect, blurRadius,
-                                               stroke, shaderCaps);
+    std::unique_ptr<GrDrawOp> op = GrShadowRRectOp::Make(paint.getColor(), viewMatrix, rrect,
+                                                         blurRadius, stroke, shaderCaps);
     if (op) {
-        GrPipelineBuilder pipelineBuilder(paint, GrAAType::kNone);
+        GrPipelineBuilder pipelineBuilder(std::move(paint), GrAAType::kNone);
         this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
         return;
     }
@@ -986,7 +988,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 bool GrRenderTargetContext::drawFilledDRRect(const GrClip& clip,
-                                             const GrPaint& paintIn,
+                                             GrPaint&& paint,
                                              GrAA aa,
                                              const SkMatrix& viewMatrix,
                                              const SkRRect& origOuter,
@@ -997,10 +999,11 @@
 
     if (GrCaps::InstancedSupport::kNone != fContext->caps()->instancedSupport()) {
         InstancedRendering* ir = this->getOpList()->instancedRendering();
-        sk_sp<GrDrawOp> op(ir->recordDRRect(origOuter, origInner, viewMatrix, paintIn.getColor(),
-                                            aa, fInstancedPipelineInfo, &aaType));
+        std::unique_ptr<GrDrawOp> op(ir->recordDRRect(origOuter, origInner, viewMatrix,
+                                                      paint.getColor(), aa, fInstancedPipelineInfo,
+                                                      &aaType));
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paintIn, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
             return true;
         }
@@ -1033,8 +1036,6 @@
         inverseVM.reset();
     }
 
-    GrPaint grPaint(paintIn);
-
     // TODO these need to be a geometry processors
     sk_sp<GrFragmentProcessor> innerEffect(GrRRectEffect::Make(innerEdgeType, *inner));
     if (!innerEffect) {
@@ -1046,20 +1047,21 @@
         return false;
     }
 
-    grPaint.addCoverageFragmentProcessor(std::move(innerEffect));
-    grPaint.addCoverageFragmentProcessor(std::move(outerEffect));
+    paint.addCoverageFragmentProcessor(std::move(innerEffect));
+    paint.addCoverageFragmentProcessor(std::move(outerEffect));
 
     SkRect bounds = outer->getBounds();
     if (GrAAType::kCoverage == aaType) {
         bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
     }
 
-    this->fillRectWithLocalMatrix(clip, grPaint, GrAA::kNo, SkMatrix::I(), bounds, inverseVM);
+    this->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), bounds,
+                                  inverseVM);
     return true;
 }
 
 void GrRenderTargetContext::drawDRRect(const GrClip& clip,
-                                       const GrPaint& paint,
+                                       GrPaint&& paint,
                                        GrAA aa,
                                        const SkMatrix& viewMatrix,
                                        const SkRRect& outer,
@@ -1074,7 +1076,7 @@
 
     AutoCheckFlush acf(fDrawingManager);
 
-    if (this->drawFilledDRRect(clip, paint, aa, viewMatrix, outer, inner)) {
+    if (this->drawFilledDRRect(clip, std::move(paint), aa, viewMatrix, outer, inner)) {
         return;
     }
 
@@ -1084,7 +1086,7 @@
     path.addRRect(outer);
     path.setFillType(SkPath::kEvenOdd_FillType);
 
-    this->internalDrawPath(clip, paint, aa, viewMatrix, path, GrStyle::SimpleFill());
+    this->internalDrawPath(clip, std::move(paint), aa, viewMatrix, path, GrStyle::SimpleFill());
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1094,7 +1096,7 @@
 }
 
 void GrRenderTargetContext::drawRegion(const GrClip& clip,
-                                       const GrPaint& paint,
+                                       GrPaint&& paint,
                                        GrAA aa,
                                        const SkMatrix& viewMatrix,
                                        const SkRegion& region,
@@ -1117,16 +1119,16 @@
     if (complexStyle || GrAA::kYes == aa) {
         SkPath path;
         region.getBoundaryPath(&path);
-        return this->drawPath(clip, paint, aa, viewMatrix, path, style);
+        return this->drawPath(clip, std::move(paint), aa, viewMatrix, path, style);
     }
 
-    sk_sp<GrDrawOp> op = GrRegionOp::Make(paint.getColor(), viewMatrix, region);
-    GrPipelineBuilder pipelineBuilder(paint, GrAAType::kNone);
+    std::unique_ptr<GrDrawOp> op = GrRegionOp::Make(paint.getColor(), viewMatrix, region);
+    GrPipelineBuilder pipelineBuilder(std::move(paint), GrAAType::kNone);
     this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
 }
 
 void GrRenderTargetContext::drawOval(const GrClip& clip,
-                                     const GrPaint& paint,
+                                     GrPaint&& paint,
                                      GrAA aa,
                                      const SkMatrix& viewMatrix,
                                      const SkRect& oval,
@@ -1149,10 +1151,10 @@
     if (GrCaps::InstancedSupport::kNone != fContext->caps()->instancedSupport() &&
         stroke.isFillStyle()) {
         InstancedRendering* ir = this->getOpList()->instancedRendering();
-        sk_sp<GrDrawOp> op(ir->recordOval(oval, viewMatrix, paint.getColor(), aa,
-                                          fInstancedPipelineInfo, &aaType));
+        std::unique_ptr<GrDrawOp> op(ir->recordOval(oval, viewMatrix, paint.getColor(), aa,
+                                                    fInstancedPipelineInfo, &aaType));
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
             return;
         }
@@ -1161,10 +1163,10 @@
     aaType = this->decideAAType(aa);
     if (GrAAType::kCoverage == aaType) {
         const GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
-        sk_sp<GrDrawOp> op =
+        std::unique_ptr<GrDrawOp> op =
                 GrOvalOpFactory::MakeOvalOp(paint.getColor(), viewMatrix, oval, stroke, shaderCaps);
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
             return;
         }
@@ -1173,11 +1175,11 @@
     SkPath path;
     path.setIsVolatile(true);
     path.addOval(oval);
-    this->internalDrawPath(clip, paint, aa, viewMatrix, path, style);
+    this->internalDrawPath(clip, std::move(paint), aa, viewMatrix, path, style);
 }
 
 void GrRenderTargetContext::drawArc(const GrClip& clip,
-                                    const GrPaint& paint,
+                                    GrPaint&& paint,
                                     GrAA aa,
                                     const SkMatrix& viewMatrix,
                                     const SkRect& oval,
@@ -1188,16 +1190,16 @@
     GrAAType aaType = this->decideAAType(aa);
     if (GrAAType::kCoverage == aaType) {
         const GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
-        sk_sp<GrDrawOp> op = GrOvalOpFactory::MakeArcOp(paint.getColor(),
-                                                        viewMatrix,
-                                                        oval,
-                                                        startAngle,
-                                                        sweepAngle,
-                                                        useCenter,
-                                                        style,
-                                                        shaderCaps);
+        std::unique_ptr<GrDrawOp> op = GrOvalOpFactory::MakeArcOp(paint.getColor(),
+                                                                  viewMatrix,
+                                                                  oval,
+                                                                  startAngle,
+                                                                  sweepAngle,
+                                                                  useCenter,
+                                                                  style,
+                                                                  shaderCaps);
         if (op) {
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
             return;
         }
@@ -1205,11 +1207,11 @@
     SkPath path;
     SkPathPriv::CreateDrawArcPath(&path, oval, startAngle, sweepAngle, useCenter,
                                   style.isSimpleFill());
-    this->internalDrawPath(clip, paint, aa, viewMatrix, path, style);
+    this->internalDrawPath(clip, std::move(paint), aa, viewMatrix, path, style);
 }
 
 void GrRenderTargetContext::drawImageLattice(const GrClip& clip,
-                                             const GrPaint& paint,
+                                             GrPaint&& paint,
                                              const SkMatrix& viewMatrix,
                                              int imageWidth,
                                              int imageHeight,
@@ -1222,10 +1224,10 @@
 
     AutoCheckFlush acf(fDrawingManager);
 
-    sk_sp<GrDrawOp> op = GrLatticeOp::MakeNonAA(paint.getColor(), viewMatrix, imageWidth,
-                                                imageHeight, std::move(iter), dst);
+    std::unique_ptr<GrDrawOp> op = GrLatticeOp::MakeNonAA(paint.getColor(), viewMatrix, imageWidth,
+                                                          imageHeight, std::move(iter), dst);
 
-    GrPipelineBuilder pipelineBuilder(paint, GrAAType::kNone);
+    GrPipelineBuilder pipelineBuilder(std::move(paint), GrAAType::kNone);
     this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
 }
 
@@ -1248,7 +1250,7 @@
 }
 
 void GrRenderTargetContext::drawNonAAFilledRect(const GrClip& clip,
-                                                const GrPaint& paint,
+                                                GrPaint&& paint,
                                                 const SkMatrix& viewMatrix,
                                                 const SkRect& rect,
                                                 const SkRect* localRect,
@@ -1257,9 +1259,9 @@
                                                 GrAAType hwOrNoneAAType) {
     SkASSERT(GrAAType::kCoverage != hwOrNoneAAType);
     SkASSERT(hwOrNoneAAType == GrAAType::kNone || this->isStencilBufferMultisampled());
-    sk_sp<GrDrawOp> op = GrRectOpFactory::MakeNonAAFill(paint.getColor(), viewMatrix, rect,
-                                                        localRect, localMatrix);
-    GrPipelineBuilder pipelineBuilder(paint, hwOrNoneAAType);
+    std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeNonAAFill(paint.getColor(), viewMatrix,
+                                                                  rect, localRect, localMatrix);
+    GrPipelineBuilder pipelineBuilder(std::move(paint), hwOrNoneAAType);
     if (ss) {
         pipelineBuilder.setUserStencil(ss);
     }
@@ -1286,8 +1288,8 @@
         return false;
     }
 
-    return rt->readPixels(x, y, dstInfo.width(), dstInfo.height(),
-                          config, dstBuffer, dstRowBytes, flags);
+    return rt->readPixels(this->getColorSpace(), x, y, dstInfo.width(), dstInfo.height(),
+                          config, dstInfo.colorSpace(), dstBuffer, dstRowBytes, flags);
 }
 
 bool GrRenderTargetContext::writePixels(const SkImageInfo& srcInfo, const void* srcBuffer,
@@ -1309,8 +1311,8 @@
         return false;
     }
 
-    return rt->writePixels(x, y, srcInfo.width(), srcInfo.height(),
-                           config, srcBuffer, srcRowBytes, flags);
+    return rt->writePixels(this->getColorSpace(), x, y, srcInfo.width(), srcInfo.height(),
+                           config, srcInfo.colorSpace(), srcBuffer, srcRowBytes, flags);
 }
 
 // Can 'path' be drawn as a pair of filled nested rectangles?
@@ -1360,7 +1362,7 @@
 }
 
 void GrRenderTargetContext::drawPath(const GrClip& clip,
-                                     const GrPaint& paint,
+                                     GrPaint&& paint,
                                      GrAA aa,
                                      const SkMatrix& viewMatrix,
                                      const SkPath& path,
@@ -1372,7 +1374,7 @@
 
     if (path.isEmpty()) {
        if (path.isInverseFillType()) {
-           this->drawPaint(clip, paint, viewMatrix);
+           this->drawPaint(clip, std::move(paint), viewMatrix);
        }
        return;
     }
@@ -1386,10 +1388,10 @@
             SkRect rects[2];
 
             if (fills_as_nested_rects(viewMatrix, path, rects)) {
-                sk_sp<GrDrawOp> op =
+                std::unique_ptr<GrDrawOp> op =
                         GrRectOpFactory::MakeAAFillNestedRects(paint.getColor(), viewMatrix, rects);
                 if (op) {
-                    GrPipelineBuilder pipelineBuilder(paint, aaType);
+                    GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
                     this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
                 }
                 return;
@@ -1400,10 +1402,10 @@
 
         if (isOval && !path.isInverseFillType()) {
             const GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
-            sk_sp<GrDrawOp> op = GrOvalOpFactory::MakeOvalOp(
+            std::unique_ptr<GrDrawOp> op = GrOvalOpFactory::MakeOvalOp(
                     paint.getColor(), viewMatrix, ovalRect, style.strokeRec(), shaderCaps);
             if (op) {
-                GrPipelineBuilder pipelineBuilder(paint, aaType);
+                GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
                 this->getOpList()->addDrawOp(pipelineBuilder, this, clip, std::move(op));
                 return;
             }
@@ -1415,7 +1417,7 @@
     // cache. This presents a potential hazard for buffered drawing. However,
     // the writePixels that uploads to the scratch will perform a flush so we're
     // OK.
-    this->internalDrawPath(clip, paint, aa, viewMatrix, path, style);
+    this->internalDrawPath(clip, std::move(paint), aa, viewMatrix, path, style);
 }
 
 bool GrRenderTargetContextPriv::drawAndStencilPath(const GrClip& clip,
@@ -1466,17 +1468,16 @@
     GrPaint paint;
     paint.setCoverageSetOpXPFactory(op, invert);
 
-    GrPathRenderer::DrawPathArgs args;
-    args.fResourceProvider =
-        fRenderTargetContext->fDrawingManager->getContext()->resourceProvider();
-    args.fPaint = &paint;
-    args.fUserStencilSettings = ss;
-    args.fRenderTargetContext = fRenderTargetContext;
-    args.fClip = &clip;
-    args.fViewMatrix = &viewMatrix;
-    args.fShape = &shape;
-    args.fAAType = aaType;
-    args.fGammaCorrect = fRenderTargetContext->isGammaCorrect();
+    GrPathRenderer::DrawPathArgs args{
+            fRenderTargetContext->fDrawingManager->getContext()->resourceProvider(),
+            std::move(paint),
+            ss,
+            fRenderTargetContext,
+            &clip,
+            &viewMatrix,
+            &shape,
+            aaType,
+            fRenderTargetContext->isGammaCorrect()};
     pr->drawPath(args);
     return true;
 }
@@ -1494,7 +1495,7 @@
 }
 
 void GrRenderTargetContext::internalDrawPath(const GrClip& clip,
-                                             const GrPaint& paint,
+                                             GrPaint&& paint,
                                              GrAA aa,
                                              const SkMatrix& viewMatrix,
                                              const SkPath& path,
@@ -1565,21 +1566,20 @@
         return;
     }
 
-    GrPathRenderer::DrawPathArgs args;
-    args.fResourceProvider = fDrawingManager->getContext()->resourceProvider();
-    args.fPaint = &paint;
-    args.fUserStencilSettings = &GrUserStencilSettings::kUnused;
-    args.fRenderTargetContext = this;
-    args.fClip = &clip;
-    args.fViewMatrix = &viewMatrix;
-    args.fShape = &shape;
-    args.fAAType = aaType;
-    args.fGammaCorrect = this->isGammaCorrect();
+    GrPathRenderer::DrawPathArgs args{fDrawingManager->getContext()->resourceProvider(),
+                                      std::move(paint),
+                                      &GrUserStencilSettings::kUnused,
+                                      this,
+                                      &clip,
+                                      &viewMatrix,
+                                      &shape,
+                                      aaType,
+                                      this->isGammaCorrect()};
     pr->drawPath(args);
 }
 
 void GrRenderTargetContext::addDrawOp(const GrPipelineBuilder& pipelineBuilder, const GrClip& clip,
-                                      sk_sp<GrDrawOp> op) {
+                                      std::unique_ptr<GrDrawOp> op) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
diff --git a/src/gpu/GrRenderTargetContextPriv.h b/src/gpu/GrRenderTargetContextPriv.h
index f1548cc..8a1061c 100644
--- a/src/gpu/GrRenderTargetContextPriv.h
+++ b/src/gpu/GrRenderTargetContextPriv.h
@@ -108,9 +108,9 @@
         return fRenderTargetContext->fRenderTargetProxy->uniqueID();
     }
 
-    void testingOnly_addDrawOp(const GrPaint&,
+    void testingOnly_addDrawOp(GrPaint&&,
                                GrAAType,
-                               sk_sp<GrDrawOp>,
+                               std::unique_ptr<GrDrawOp>,
                                const GrUserStencilSettings* = nullptr,
                                bool snapToCenters = false);
 
diff --git a/src/gpu/GrRenderTargetOpList.cpp b/src/gpu/GrRenderTargetOpList.cpp
index 7ca7250..9fb2dcb 100644
--- a/src/gpu/GrRenderTargetOpList.cpp
+++ b/src/gpu/GrRenderTargetOpList.cpp
@@ -50,7 +50,6 @@
                                            GrResourceProvider* resourceProvider,
                                            GrAuditTrail* auditTrail, const Options& options)
     : INHERITED(rtp, auditTrail)
-    , fLastFullClearOp(nullptr)
     , fGpu(SkRef(gpu))
     , fResourceProvider(resourceProvider)
     , fLastClipStackGenID(SK_InvalidUniqueID) {
@@ -185,13 +184,13 @@
         if (!fRecordedOps[i].fOp) {
             continue;
         }
-        if (fRecordedOps[i].fOp->renderTargetUniqueID() != currentRTID) {
+        if (fRecordedOps[i].fRenderTargetID != currentRTID) {
             if (commandBuffer) {
                 commandBuffer->end();
                 commandBuffer->submit();
                 commandBuffer.reset();
             }
-            currentRTID = fRecordedOps[i].fOp->renderTargetUniqueID();
+            currentRTID = fRecordedOps[i].fRenderTargetID;
             if (!currentRTID.isInvalid()) {
                 static const GrGpuCommandBuffer::LoadAndStoreInfo kBasicLoadStoreInfo
                     { GrGpuCommandBuffer::LoadOp::kLoad,GrGpuCommandBuffer::StoreOp::kStore,
@@ -215,6 +214,7 @@
 
 void GrRenderTargetOpList::reset() {
     fLastFullClearOp = nullptr;
+    fLastFullClearRenderTargetID.makeInvalid();
     fRecordedOps.reset();
     if (fInstancedRendering) {
         fInstancedRendering->endFlush();
@@ -264,7 +264,8 @@
 void GrRenderTargetOpList::addDrawOp(const GrPipelineBuilder& pipelineBuilder,
                                      GrRenderTargetContext* renderTargetContext,
                                      const GrClip& clip,
-                                     sk_sp<GrDrawOp> op) {
+                                     std::unique_ptr<GrDrawOp>
+                                             op) {
     // Setup clip
     SkRect bounds;
     op_bounds(&bounds, op.get());
@@ -346,7 +347,7 @@
     SkASSERT(fSurface);
     op->pipeline()->addDependenciesTo(fSurface);
 #endif
-    this->recordOp(std::move(op), appliedClip.clippedDrawBounds());
+    this->recordOp(std::move(op), renderTargetContext, appliedClip.clippedDrawBounds());
 }
 
 void GrRenderTargetOpList::stencilPath(GrRenderTargetContext* renderTargetContext,
@@ -384,42 +385,44 @@
         return;
     }
 
-    sk_sp<GrOp> op = GrStencilPathOp::Make(viewMatrix,
-                                           useHWAA,
-                                           path->getFillType(),
-                                           appliedClip.hasStencilClip(),
-                                           stencilAttachment->bits(),
-                                           appliedClip.scissorState(),
-                                           renderTargetContext->accessRenderTarget(),
-                                           path);
-    this->recordOp(std::move(op), appliedClip.clippedDrawBounds());
+    std::unique_ptr<GrOp> op = GrStencilPathOp::Make(viewMatrix,
+                                                     useHWAA,
+                                                     path->getFillType(),
+                                                     appliedClip.hasStencilClip(),
+                                                     stencilAttachment->bits(),
+                                                     appliedClip.scissorState(),
+                                                     renderTargetContext->accessRenderTarget(),
+                                                     path);
+    this->recordOp(std::move(op), renderTargetContext, appliedClip.clippedDrawBounds());
 }
 
-void GrRenderTargetOpList::fullClear(GrRenderTarget* renderTarget, GrColor color) {
+void GrRenderTargetOpList::fullClear(GrRenderTargetContext* renderTargetContext, GrColor color) {
+    GrRenderTarget* renderTarget = renderTargetContext->accessRenderTarget();
     // Currently this just inserts or updates the last clear op. However, once in MDB this can
     // remove all the previously recorded ops and change the load op to clear with supplied
     // color.
     // TODO: this needs to be updated to use GrSurfaceProxy::UniqueID
-    if (fLastFullClearOp &&
-        fLastFullClearOp->renderTargetUniqueID() == renderTarget->uniqueID()) {
+    if (fLastFullClearRenderTargetID == renderTarget->uniqueID()) {
         // As currently implemented, fLastFullClearOp should be the last op because we would
         // have cleared it when another op was recorded.
         SkASSERT(fRecordedOps.back().fOp.get() == fLastFullClearOp);
         fLastFullClearOp->setColor(color);
         return;
     }
-    sk_sp<GrClearOp> op(GrClearOp::Make(GrFixedClip::Disabled(), color, renderTarget));
-    if (GrOp* clearOp = this->recordOp(std::move(op))) {
+    std::unique_ptr<GrClearOp> op(GrClearOp::Make(GrFixedClip::Disabled(), color, renderTarget));
+    if (GrOp* clearOp = this->recordOp(std::move(op), renderTargetContext)) {
         // This is either the clear op we just created or another one that it combined with.
         fLastFullClearOp = static_cast<GrClearOp*>(clearOp);
+        fLastFullClearRenderTargetID = renderTarget->uniqueID();
     }
 }
 
-void GrRenderTargetOpList::discard(GrRenderTarget* renderTarget) {
+void GrRenderTargetOpList::discard(GrRenderTargetContext* renderTargetContext) {
     // Currently this just inserts a discard op. However, once in MDB this can remove all the
     // previously recorded ops and change the load op to discard.
     if (this->caps()->discardRenderTargetSupport()) {
-        this->recordOp(GrDiscardOp::Make(renderTarget));
+        this->recordOp(GrDiscardOp::Make(renderTargetContext->accessRenderTarget()),
+                       renderTargetContext);
     }
 }
 
@@ -429,7 +432,7 @@
                                        GrSurface* src,
                                        const SkIRect& srcRect,
                                        const SkIPoint& dstPoint) {
-    sk_sp<GrOp> op = GrCopySurfaceOp::Make(dst, src, srcRect, dstPoint);
+    std::unique_ptr<GrOp> op = GrCopySurfaceOp::Make(dst, src, srcRect, dstPoint);
     if (!op) {
         return false;
     }
@@ -437,7 +440,10 @@
     this->addDependency(src);
 #endif
 
-    this->recordOp(std::move(op));
+    // Copy surface doesn't work through a GrGpuCommandBuffer. By passing nullptr for the context we
+    // force this to occur between command buffers and execute directly on GrGpu. This workaround
+    // goes away with MDB.
+    this->recordOp(std::move(op), nullptr);
     return true;
 }
 
@@ -455,7 +461,14 @@
     out->fBottom = SkTMax(a.fBottom, b.fBottom);
 }
 
-GrOp* GrRenderTargetOpList::recordOp(sk_sp<GrOp> op, const SkRect& clippedBounds) {
+GrOp* GrRenderTargetOpList::recordOp(std::unique_ptr<GrOp> op,
+                                     GrRenderTargetContext* renderTargetContext,
+                                     const SkRect& clippedBounds) {
+    // TODO: Should be proxy ID.
+    GrGpuResource::UniqueID renderTargetID =
+            renderTargetContext ? renderTargetContext->accessRenderTarget()->uniqueID()
+                                : GrGpuResource::UniqueID::InvalidID();
+
     // A closed GrOpList should never receive new/more ops
     SkASSERT(!this->isClosed());
 
@@ -463,7 +476,7 @@
     // 1) check every op
     // 2) intersect with something
     // 3) find a 'blocker'
-    GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get());
+    GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get(), renderTargetID);
     GrOP_INFO("Re-Recording (%s, B%u)\n"
               "\tBounds LRTB (%f, %f, %f, %f)\n",
                op->name(),
@@ -476,29 +489,30 @@
               clippedBounds.fBottom);
     GrOP_INFO("\tOutcome:\n");
     int maxCandidates = SkTMin(fMaxOpLookback, fRecordedOps.count());
-    if (maxCandidates) {
+    // If we don't have a valid destination render target ID then we cannot reorder.
+    if (maxCandidates && !renderTargetID.isInvalid()) {
         int i = 0;
         while (true) {
-            GrOp* candidate = fRecordedOps.fromBack(i).fOp.get();
+            const RecordedOp& candidate = fRecordedOps.fromBack(i);
             // We cannot continue to search backwards if the render target changes
-            if (candidate->renderTargetUniqueID() != op->renderTargetUniqueID()) {
-                GrOP_INFO("\t\tBreaking because of (%s, B%u) Rendertarget\n",
-                          candidate->name(), candidate->uniqueID());
+            if (candidate.fRenderTargetID != renderTargetID) {
+                GrOP_INFO("\t\tBreaking because of (%s, B%u) Rendertarget\n", candidate.fOp->name(),
+                          candidate.fOp->uniqueID());
                 break;
             }
-            if (candidate->combineIfPossible(op.get(), *this->caps())) {
-                GrOP_INFO("\t\tCombining with (%s, B%u)\n", candidate->name(),
-                          candidate->uniqueID());
-                GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(fAuditTrail, candidate, op.get());
+            if (candidate.fOp->combineIfPossible(op.get(), *this->caps())) {
+                GrOP_INFO("\t\tCombining with (%s, B%u)\n", candidate.fOp->name(),
+                          candidate.fOp->uniqueID());
+                GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(fAuditTrail, candidate.fOp.get(), op.get());
                 join(&fRecordedOps.fromBack(i).fClippedBounds,
                      fRecordedOps.fromBack(i).fClippedBounds, clippedBounds);
-                return candidate;
+                return candidate.fOp.get();
             }
             // Stop going backwards if we would cause a painter's order violation.
             const SkRect& candidateBounds = fRecordedOps.fromBack(i).fClippedBounds;
             if (!can_reorder(candidateBounds, clippedBounds)) {
-                GrOP_INFO("\t\tIntersects with (%s, B%u)\n", candidate->name(),
-                          candidate->uniqueID());
+                GrOP_INFO("\t\tIntersects with (%s, B%u)\n", candidate.fOp->name(),
+                          candidate.fOp->uniqueID());
                 break;
             }
             ++i;
@@ -511,8 +525,9 @@
         GrOP_INFO("\t\tFirstOp\n");
     }
     GR_AUDIT_TRAIL_OP_RESULT_NEW(fAuditTrail, op);
-    fRecordedOps.emplace_back(RecordedOp{std::move(op), clippedBounds});
+    fRecordedOps.emplace_back(RecordedOp{std::move(op), clippedBounds, renderTargetID});
     fLastFullClearOp = nullptr;
+    fLastFullClearRenderTargetID.makeInvalid();
     return fRecordedOps.back().fOp.get();
 }
 
@@ -522,25 +537,30 @@
     }
     for (int i = 0; i < fRecordedOps.count() - 2; ++i) {
         GrOp* op = fRecordedOps[i].fOp.get();
+        GrGpuResource::UniqueID renderTargetID = fRecordedOps[i].fRenderTargetID;
+        // If we don't have a valid destination render target ID then we cannot reorder.
+        if (renderTargetID.isInvalid()) {
+            continue;
+        }
         const SkRect& opBounds = fRecordedOps[i].fClippedBounds;
         int maxCandidateIdx = SkTMin(i + fMaxOpLookahead, fRecordedOps.count() - 1);
         int j = i + 1;
         while (true) {
-            GrOp* candidate = fRecordedOps[j].fOp.get();
+            const RecordedOp& candidate = fRecordedOps[j];
             // We cannot continue to search if the render target changes
-            if (candidate->renderTargetUniqueID() != op->renderTargetUniqueID()) {
-                GrOP_INFO("\t\tBreaking because of (%s, B%u) Rendertarget\n",
-                          candidate->name(), candidate->uniqueID());
+            if (candidate.fRenderTargetID != renderTargetID) {
+                GrOP_INFO("\t\tBreaking because of (%s, B%u) Rendertarget\n", candidate.fOp->name(),
+                          candidate.fOp->uniqueID());
                 break;
             }
             if (j == i +1) {
                 // We assume op would have combined with candidate when the candidate was added
                 // via backwards combining in recordOp.
-                SkASSERT(!op->combineIfPossible(candidate, *this->caps()));
-            } else if (op->combineIfPossible(candidate, *this->caps())) {
-                GrOP_INFO("\t\tCombining with (%s, B%u)\n", candidate->name(),
-                          candidate->uniqueID());
-                GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(fAuditTrail, op, candidate);
+                SkASSERT(!op->combineIfPossible(candidate.fOp.get(), *this->caps()));
+            } else if (op->combineIfPossible(candidate.fOp.get(), *this->caps())) {
+                GrOP_INFO("\t\tCombining with (%s, B%u)\n", candidate.fOp->name(),
+                          candidate.fOp->uniqueID());
+                GR_AUDIT_TRAIL_OPS_RESULT_COMBINED(fAuditTrail, op, candidate.fOp.get());
                 fRecordedOps[j].fOp = std::move(fRecordedOps[i].fOp);
                 join(&fRecordedOps[j].fClippedBounds, fRecordedOps[j].fClippedBounds, opBounds);
                 break;
@@ -548,8 +568,8 @@
             // Stop going traversing if we would cause a painter's order violation.
             const SkRect& candidateBounds = fRecordedOps[j].fClippedBounds;
             if (!can_reorder(candidateBounds, opBounds)) {
-                GrOP_INFO("\t\tIntersects with (%s, B%u)\n", candidate->name(),
-                          candidate->uniqueID());
+                GrOP_INFO("\t\tIntersects with (%s, B%u)\n", candidate.fOp->name(),
+                          candidate.fOp->uniqueID());
                 break;
             }
             ++j;
@@ -565,6 +585,8 @@
 
 void GrRenderTargetOpList::clearStencilClip(const GrFixedClip& clip,
                                             bool insideStencilMask,
-                                            GrRenderTarget* rt) {
-    this->recordOp(GrClearStencilClipOp::Make(clip, insideStencilMask, rt));
+                                            GrRenderTargetContext* renderTargetContext) {
+    this->recordOp(GrClearStencilClipOp::Make(clip, insideStencilMask,
+                                              renderTargetContext->accessRenderTarget()),
+                   renderTargetContext);
 }
diff --git a/src/gpu/GrRenderTargetOpList.h b/src/gpu/GrRenderTargetOpList.h
index a2b323f..216a330 100644
--- a/src/gpu/GrRenderTargetOpList.h
+++ b/src/gpu/GrRenderTargetOpList.h
@@ -79,9 +79,11 @@
     const GrCaps* caps() const { return fGpu->caps(); }
 
     void addDrawOp(const GrPipelineBuilder&, GrRenderTargetContext*, const GrClip&,
-                   sk_sp<GrDrawOp>);
+                   std::unique_ptr<GrDrawOp>);
 
-    void addOp(sk_sp<GrOp> op) { this->recordOp(std::move(op)); }
+    void addOp(std::unique_ptr<GrOp> op, GrRenderTargetContext* renderTargetContext) {
+        this->recordOp(std::move(op), renderTargetContext);
+    }
 
     /**
      * Draws the path into user stencil bits. Upon return, all user stencil values
@@ -97,10 +99,10 @@
                      const GrPath*);
 
     /** Clears the entire render target */
-    void fullClear(GrRenderTarget*, GrColor color);
+    void fullClear(GrRenderTargetContext*, GrColor color);
 
     /** Discards the contents render target. */
-    void discard(GrRenderTarget*);
+    void discard(GrRenderTargetContext*);
 
     /**
      * Copies a pixel rectangle from one surface to another. This call may finalize
@@ -131,13 +133,13 @@
 
     // If the input op is combined with an earlier op, this returns the combined op. Otherwise, it
     // returns the input op.
-    GrOp* recordOp(sk_sp<GrOp> op) {
+    GrOp* recordOp(std::unique_ptr<GrOp> op, GrRenderTargetContext* renderTargetContext) {
         SkRect bounds = op->bounds();
-        return this->recordOp(std::move(op), bounds);
+        return this->recordOp(std::move(op), renderTargetContext, bounds);
     }
 
     // Variant that allows an explicit bounds (computed from the Op's bounds and a clip).
-    GrOp* recordOp(sk_sp<GrOp>, const SkRect& clippedBounds);
+    GrOp* recordOp(std::unique_ptr<GrOp>, GrRenderTargetContext*, const SkRect& clippedBounds);
 
     void forwardCombine();
 
@@ -150,14 +152,19 @@
                          GrXferProcessor::DstTexture*);
 
     // Used only via GrRenderTargetContextPriv.
-    void clearStencilClip(const GrFixedClip&, bool insideStencilMask, GrRenderTarget*);
+    void clearStencilClip(const GrFixedClip&, bool insideStencilMask, GrRenderTargetContext*);
 
     struct RecordedOp {
-        sk_sp<GrOp> fOp;
+        std::unique_ptr<GrOp> fOp;
         SkRect fClippedBounds;
+        // TODO: Use proxy ID instead of instantiated render target ID.
+        GrGpuResource::UniqueID fRenderTargetID;
     };
     SkSTArray<256, RecordedOp, true> fRecordedOps;
-    GrClearOp* fLastFullClearOp;
+
+    GrClearOp* fLastFullClearOp = nullptr;
+    GrGpuResource::UniqueID fLastFullClearRenderTargetID = GrGpuResource::UniqueID::InvalidID();
+
     // The context is only in service of the GrClip, remove once it doesn't need this.
     GrContext* fContext;
     GrGpu* fGpu;
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index ae9a4e7..5f08a51 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -211,6 +211,10 @@
     void dumpStatsKeyValuePairs(SkTArray<SkString>* keys, SkTArray<double>* value) const;
 #endif
 
+#ifdef SK_DEBUG
+    int countUniqueKeysWithTag(const char* tag) const;
+#endif
+
     // This function is for unit testing and is only defined in test tools.
     void changeTimestamp(uint32_t newTimestamp);
 
diff --git a/src/gpu/GrResourceProvider.h b/src/gpu/GrResourceProvider.h
index 58bd1f8..47490e1 100644
--- a/src/gpu/GrResourceProvider.h
+++ b/src/gpu/GrResourceProvider.h
@@ -89,6 +89,7 @@
     GrPathRange* createGlyphs(const SkTypeface*, const SkScalerContextEffects&,
                               const SkDescriptor*, const GrStyle&);
 
+    using GrTextureProvider::createTexture;
     using GrTextureProvider::assignUniqueKeyToResource;
     using GrTextureProvider::findAndRefResourceByUniqueKey;
     using GrTextureProvider::findAndRefTextureByUniqueKey;
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index be56ad5..839ec25 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -160,7 +160,7 @@
 
 void GrSWMaskHelper::DrawToTargetWithShapeMask(GrTexture* texture,
                                                GrRenderTargetContext* renderTargetContext,
-                                               const GrPaint& paint,
+                                               GrPaint&& paint,
                                                const GrUserStencilSettings& userStencilSettings,
                                                const GrClip& clip,
                                                const SkMatrix& viewMatrix,
@@ -181,16 +181,14 @@
     maskMatrix.preTranslate(SkIntToScalar(-textureOriginInDeviceSpace.fX),
                             SkIntToScalar(-textureOriginInDeviceSpace.fY));
     maskMatrix.preConcat(viewMatrix);
-    GrPipelineBuilder pipelineBuilder(paint, GrAAType::kNone);
+    std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeNonAAFill(paint.getColor(), SkMatrix::I(),
+                                                                  dstRect, nullptr, &invert);
+    GrPipelineBuilder pipelineBuilder(std::move(paint), GrAAType::kNone);
     pipelineBuilder.setUserStencil(&userStencilSettings);
-
     pipelineBuilder.addCoverageFragmentProcessor(
                          GrSimpleTextureEffect::Make(texture,
                                                      nullptr,
                                                      maskMatrix,
                                                      GrSamplerParams::kNone_FilterMode));
-
-    sk_sp<GrDrawOp> op = GrRectOpFactory::MakeNonAAFill(paint.getColor(), SkMatrix::I(), dstRect,
-                                                        nullptr, &invert);
     renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
 }
diff --git a/src/gpu/GrSWMaskHelper.h b/src/gpu/GrSWMaskHelper.h
index 6ec1c82..89d8842 100644
--- a/src/gpu/GrSWMaskHelper.h
+++ b/src/gpu/GrSWMaskHelper.h
@@ -81,7 +81,7 @@
     // local coords are provided to any fragment processors in the paint.
     static void DrawToTargetWithShapeMask(GrTexture* texture,
                                           GrRenderTargetContext*,
-                                          const GrPaint& paint,
+                                          GrPaint&& paint,
                                           const GrUserStencilSettings& userStencilSettings,
                                           const GrClip&,
                                           const SkMatrix& viewMatrix,
diff --git a/src/gpu/GrShaderCaps.cpp b/src/gpu/GrShaderCaps.cpp
index 3e6323f..f9db968 100644
--- a/src/gpu/GrShaderCaps.cpp
+++ b/src/gpu/GrShaderCaps.cpp
@@ -202,7 +202,7 @@
         uint8_t* table = fSamplerPrecisions[visibility];
         table[kUnknown_GrPixelConfig]        = kDefault_GrSLPrecision;
         table[kAlpha_8_GrPixelConfig]        = lowp;
-        table[kIndex_8_GrPixelConfig]        = lowp;
+        table[kGray_8_GrPixelConfig]         = lowp;
         table[kRGB_565_GrPixelConfig]        = lowp;
         table[kRGBA_4444_GrPixelConfig]      = lowp;
         table[kRGBA_8888_GrPixelConfig]      = lowp;
diff --git a/src/gpu/GrSoftwarePathRenderer.cpp b/src/gpu/GrSoftwarePathRenderer.cpp
index fc529ba..695796b 100644
--- a/src/gpu/GrSoftwarePathRenderer.cpp
+++ b/src/gpu/GrSoftwarePathRenderer.cpp
@@ -71,22 +71,22 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 void GrSoftwarePathRenderer::DrawNonAARect(GrRenderTargetContext* renderTargetContext,
-                                           const GrPaint& paint,
+                                           GrPaint&& paint,
                                            const GrUserStencilSettings& userStencilSettings,
                                            const GrClip& clip,
                                            const SkMatrix& viewMatrix,
                                            const SkRect& rect,
                                            const SkMatrix& localMatrix) {
-    sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(paint.getColor(), viewMatrix, rect, nullptr,
-                                                      &localMatrix));
+    std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(paint.getColor(), viewMatrix, rect,
+                                                                nullptr, &localMatrix));
 
-    GrPipelineBuilder pipelineBuilder(paint, GrAAType::kNone);
+    GrPipelineBuilder pipelineBuilder(std::move(paint), GrAAType::kNone);
     pipelineBuilder.setUserStencil(&userStencilSettings);
     renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
 }
 
 void GrSoftwarePathRenderer::DrawAroundInvPath(GrRenderTargetContext* renderTargetContext,
-                                               const GrPaint& paint,
+                                               GrPaint&& paint,
                                                const GrUserStencilSettings& userStencilSettings,
                                                const GrClip& clip,
                                                const SkMatrix& viewMatrix,
@@ -101,25 +101,25 @@
     if (devClipBounds.fTop < devPathBounds.fTop) {
         rect.iset(devClipBounds.fLeft, devClipBounds.fTop,
                   devClipBounds.fRight, devPathBounds.fTop);
-        DrawNonAARect(renderTargetContext, paint, userStencilSettings, clip,
-                      SkMatrix::I(), rect, invert);
+        DrawNonAARect(renderTargetContext, GrPaint(paint), userStencilSettings, clip, SkMatrix::I(),
+                      rect, invert);
     }
     if (devClipBounds.fLeft < devPathBounds.fLeft) {
         rect.iset(devClipBounds.fLeft, devPathBounds.fTop,
                   devPathBounds.fLeft, devPathBounds.fBottom);
-        DrawNonAARect(renderTargetContext, paint, userStencilSettings, clip,
-                      SkMatrix::I(), rect, invert);
+        DrawNonAARect(renderTargetContext, GrPaint(paint), userStencilSettings, clip, SkMatrix::I(),
+                      rect, invert);
     }
     if (devClipBounds.fRight > devPathBounds.fRight) {
         rect.iset(devPathBounds.fRight, devPathBounds.fTop,
                   devClipBounds.fRight, devPathBounds.fBottom);
-        DrawNonAARect(renderTargetContext, paint, userStencilSettings, clip,
-                      SkMatrix::I(), rect, invert);
+        DrawNonAARect(renderTargetContext, GrPaint(paint), userStencilSettings, clip, SkMatrix::I(),
+                      rect, invert);
     }
     if (devClipBounds.fBottom > devPathBounds.fBottom) {
         rect.iset(devClipBounds.fLeft, devPathBounds.fBottom,
                   devClipBounds.fRight, devClipBounds.fBottom);
-        DrawNonAARect(renderTargetContext, paint, userStencilSettings, clip,
+        DrawNonAARect(renderTargetContext, std::move(paint), userStencilSettings, clip,
                       SkMatrix::I(), rect, invert);
     }
 }
@@ -152,10 +152,9 @@
                                    &clippedDevShapeBounds,
                                    &devClipBounds)) {
         if (inverseFilled) {
-            DrawAroundInvPath(args.fRenderTargetContext, *args.fPaint, *args.fUserStencilSettings,
-                              *args.fClip,
-                              *args.fViewMatrix, devClipBounds, unclippedDevShapeBounds);
-
+            DrawAroundInvPath(args.fRenderTargetContext, std::move(args.fPaint),
+                              *args.fUserStencilSettings, *args.fClip, *args.fViewMatrix,
+                              devClipBounds, unclippedDevShapeBounds);
         }
         return true;
     }
@@ -223,16 +222,15 @@
             texture->resourcePriv().setUniqueKey(maskKey);
         }
     }
-    GrSWMaskHelper::DrawToTargetWithShapeMask(texture.get(), args.fRenderTargetContext,
-                                              *args.fPaint, *args.fUserStencilSettings,
-                                              *args.fClip, *args.fViewMatrix,
-                                              SkIPoint {boundsForMask->fLeft, boundsForMask->fTop},
-                                              *boundsForMask);
     if (inverseFilled) {
-        DrawAroundInvPath(args.fRenderTargetContext, *args.fPaint, *args.fUserStencilSettings,
-                          *args.fClip,
-                          *args.fViewMatrix, devClipBounds, unclippedDevShapeBounds);
+        DrawAroundInvPath(args.fRenderTargetContext, GrPaint(args.fPaint),
+                          *args.fUserStencilSettings, *args.fClip, *args.fViewMatrix, devClipBounds,
+                          unclippedDevShapeBounds);
     }
+    GrSWMaskHelper::DrawToTargetWithShapeMask(
+            texture.get(), args.fRenderTargetContext, std::move(args.fPaint),
+            *args.fUserStencilSettings, *args.fClip, *args.fViewMatrix,
+            SkIPoint{boundsForMask->fLeft, boundsForMask->fTop}, *boundsForMask);
 
     return true;
 }
diff --git a/src/gpu/GrSoftwarePathRenderer.h b/src/gpu/GrSoftwarePathRenderer.h
index 64ecfb6..7dec4be 100644
--- a/src/gpu/GrSoftwarePathRenderer.h
+++ b/src/gpu/GrSoftwarePathRenderer.h
@@ -23,14 +23,14 @@
             , fAllowCaching(allowCaching) {}
 private:
     static void DrawNonAARect(GrRenderTargetContext* renderTargetContext,
-                              const GrPaint& paint,
+                              GrPaint&& paint,
                               const GrUserStencilSettings& userStencilSettings,
                               const GrClip& clip,
                               const SkMatrix& viewMatrix,
                               const SkRect& rect,
                               const SkMatrix& localMatrix);
     static void DrawAroundInvPath(GrRenderTargetContext* renderTargetContext,
-                                  const GrPaint& paint,
+                                  GrPaint&& paint,
                                   const GrUserStencilSettings& userStencilSettings,
                                   const GrClip& clip,
                                   const SkMatrix& viewMatrix,
diff --git a/src/gpu/GrSurface.cpp b/src/gpu/GrSurface.cpp
index b978789..c3ac224 100644
--- a/src/gpu/GrSurface.cpp
+++ b/src/gpu/GrSurface.cpp
@@ -138,28 +138,28 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-bool GrSurface::writePixels(int left, int top, int width, int height,
-                            GrPixelConfig config, const void* buffer, size_t rowBytes,
-                            uint32_t pixelOpsFlags) {
+bool GrSurface::writePixels(SkColorSpace* dstColorSpace, int left, int top, int width, int height,
+                            GrPixelConfig config, SkColorSpace* srcColorSpace, const void* buffer,
+                            size_t rowBytes, uint32_t pixelOpsFlags) {
     // go through context so that all necessary flushing occurs
     GrContext* context = this->getContext();
     if (nullptr == context) {
         return false;
     }
-    return context->writeSurfacePixels(this, left, top, width, height, config, buffer,
-                                       rowBytes, pixelOpsFlags);
+    return context->writeSurfacePixels(this, dstColorSpace, left, top, width, height, config,
+                                       srcColorSpace, buffer, rowBytes, pixelOpsFlags);
 }
 
-bool GrSurface::readPixels(int left, int top, int width, int height,
-                           GrPixelConfig config, void* buffer, size_t rowBytes,
-                           uint32_t pixelOpsFlags) {
+bool GrSurface::readPixels(SkColorSpace* srcColorSpace, int left, int top, int width, int height,
+                           GrPixelConfig config, SkColorSpace* dstColorSpace, void* buffer,
+                           size_t rowBytes, uint32_t pixelOpsFlags) {
     // go through context so that all necessary flushing occurs
     GrContext* context = this->getContext();
     if (nullptr == context) {
         return false;
     }
-    return context->readSurfacePixels(this, left, top, width, height, config, buffer,
-                                      rowBytes, pixelOpsFlags);
+    return context->readSurfacePixels(this, srcColorSpace, left, top, width, height, config,
+                                      dstColorSpace, buffer, rowBytes, pixelOpsFlags);
 }
 
 void GrSurface::flushWrites() {
diff --git a/src/gpu/GrTessellator.cpp b/src/gpu/GrTessellator.cpp
index e75e1bb..09a5662 100644
--- a/src/gpu/GrTessellator.cpp
+++ b/src/gpu/GrTessellator.cpp
@@ -245,6 +245,9 @@
     void prepend(Vertex* v) {
         insert(v, nullptr, fHead);
     }
+    void remove(Vertex* v) {
+        list_remove<Vertex, &Vertex::fPrev, &Vertex::fNext>(v, &fHead, &fTail);
+    }
     void close() {
         if (fHead && fTail) {
             fTail->fNext = fHead;
@@ -309,10 +312,12 @@
  */
 
 struct Edge {
-    Edge(Vertex* top, Vertex* bottom, int winding)
+    enum class Type { kInner, kOuter, kConnector };
+    Edge(Vertex* top, Vertex* bottom, int winding, Type type)
         : fWinding(winding)
         , fTop(top)
         , fBottom(bottom)
+        , fType(type)
         , fLeft(nullptr)
         , fRight(nullptr)
         , fPrevEdgeAbove(nullptr)
@@ -332,6 +337,7 @@
     int      fWinding;          // 1 == edge goes downward; -1 = edge goes upward.
     Vertex*  fTop;              // The top vertex in vertex-sort-order (sweep_lt).
     Vertex*  fBottom;           // The bottom vertex in vertex-sort-order.
+    Type     fType;
     Edge*    fLeft;             // The linked list of edges in the active edge list.
     Edge*    fRight;            // "
     Edge*    fPrevEdgeAbove;    // The linked list of edges in the bottom Vertex's "edges above".
@@ -531,7 +537,8 @@
             fTail->addEdge(e);
             fCount++;
         } else {
-            e = ALLOC_NEW(Edge, (fTail->fLastEdge->fBottom, e->fBottom, 1), alloc);
+            e = ALLOC_NEW(Edge, (fTail->fLastEdge->fBottom, e->fBottom, 1, Edge::Type::kInner),
+                          alloc);
             fTail->addEdge(e);
             fCount++;
             if (partner) {
@@ -767,12 +774,11 @@
     }
 }
 
-Edge* new_edge(Vertex* prev, Vertex* next, SkChunkAlloc& alloc, Comparator& c,
-               int winding_scale = 1) {
-    int winding = c.sweep_lt(prev->fPoint, next->fPoint) ? winding_scale : -winding_scale;
+Edge* new_edge(Vertex* prev, Vertex* next, Edge::Type type, Comparator& c, SkChunkAlloc& alloc) {
+    int winding = c.sweep_lt(prev->fPoint, next->fPoint) ? 1 : -1;
     Vertex* top = winding < 0 ? next : prev;
     Vertex* bottom = winding < 0 ? prev : next;
-    return ALLOC_NEW(Edge, (top, bottom, winding), alloc);
+    return ALLOC_NEW(Edge, (top, bottom, winding, type), alloc);
 }
 
 void remove_edge(Edge* edge, EdgeList* edges) {
@@ -825,7 +831,10 @@
 }
 
 void fix_active_state(Edge* edge, EdgeList* activeEdges, Comparator& c) {
-    if (activeEdges && activeEdges->contains(edge)) {
+    if (!activeEdges) {
+        return;
+    }
+    if (activeEdges->contains(edge)) {
         if (edge->fBottom->fProcessed || !edge->fTop->fProcessed) {
             remove_edge(edge, activeEdges);
         }
@@ -887,13 +896,15 @@
         edge, &edge->fTop->fFirstEdgeBelow, &edge->fTop->fLastEdgeBelow);
 }
 
-void erase_edge_if_zero_winding(Edge* edge, EdgeList* edges) {
-    if (edge->fWinding != 0) {
-        return;
-    }
-    LOG("erasing edge (%g -> %g)\n", edge->fTop->fID, edge->fBottom->fID);
+void disconnect(Edge* edge)
+{
     remove_edge_above(edge);
     remove_edge_below(edge);
+}
+
+void erase_edge(Edge* edge, EdgeList* edges) {
+    LOG("erasing edge (%g -> %g)\n", edge->fTop->fID, edge->fBottom->fID);
+    disconnect(edge);
     if (edges && edges->contains(edge)) {
         remove_edge(edge, edges);
     }
@@ -925,16 +936,12 @@
             edge->fTop->fPoint.fX, edge->fTop->fPoint.fY,
             edge->fBottom->fPoint.fX, edge->fBottom->fPoint.fY);
         other->fWinding += edge->fWinding;
-        erase_edge_if_zero_winding(other, activeEdges);
-        edge->fWinding = 0;
-        erase_edge_if_zero_winding(edge, activeEdges);
+        erase_edge(edge, activeEdges);
     } else if (c.sweep_lt(edge->fTop->fPoint, other->fTop->fPoint)) {
         other->fWinding += edge->fWinding;
-        erase_edge_if_zero_winding(other, activeEdges);
         set_bottom(edge, other->fTop, activeEdges, c);
     } else {
         edge->fWinding += other->fWinding;
-        erase_edge_if_zero_winding(edge, activeEdges);
         set_bottom(other, edge->fTop, activeEdges, c);
     }
 }
@@ -945,16 +952,12 @@
             edge->fTop->fPoint.fX, edge->fTop->fPoint.fY,
             edge->fBottom->fPoint.fX, edge->fBottom->fPoint.fY);
         other->fWinding += edge->fWinding;
-        erase_edge_if_zero_winding(other, activeEdges);
-        edge->fWinding = 0;
-        erase_edge_if_zero_winding(edge, activeEdges);
+        erase_edge(edge, activeEdges);
     } else if (c.sweep_lt(edge->fBottom->fPoint, other->fBottom->fPoint)) {
         edge->fWinding += other->fWinding;
-        erase_edge_if_zero_winding(edge, activeEdges);
         set_top(other, edge->fBottom, activeEdges, c);
     } else {
         other->fWinding += edge->fWinding;
-        erase_edge_if_zero_winding(other, activeEdges);
         set_top(edge, other->fBottom, activeEdges, c);
     }
 }
@@ -1021,7 +1024,7 @@
     } else if (c.sweep_gt(v->fPoint, edge->fBottom->fPoint)) {
         set_bottom(edge, v, activeEdges, c);
     } else {
-        Edge* newEdge = ALLOC_NEW(Edge, (v, edge->fBottom, edge->fWinding), alloc);
+        Edge* newEdge = ALLOC_NEW(Edge, (v, edge->fBottom, edge->fWinding, edge->fType), alloc);
         insert_edge_below(newEdge, v, c);
         insert_edge_above(newEdge, edge->fBottom, c);
         set_bottom(edge, v, activeEdges, c);
@@ -1031,9 +1034,8 @@
     }
 }
 
-Edge* connect(Vertex* prev, Vertex* next, SkChunkAlloc& alloc, Comparator c,
-              int winding_scale = 1) {
-    Edge* edge = new_edge(prev, next, alloc, c, winding_scale);
+Edge* connect(Vertex* prev, Vertex* next, Edge::Type type, Comparator& c, SkChunkAlloc& alloc) {
+    Edge* edge = new_edge(prev, next, type, c, alloc);
     if (edge->fWinding > 0) {
         insert_edge_below(edge, prev, c);
         insert_edge_above(edge, next, c);
@@ -1045,7 +1047,8 @@
     return edge;
 }
 
-void merge_vertices(Vertex* src, Vertex* dst, Vertex** head, Comparator& c, SkChunkAlloc& alloc) {
+void merge_vertices(Vertex* src, Vertex* dst, VertexList* mesh, Comparator& c,
+                    SkChunkAlloc& alloc) {
     LOG("found coincident verts at %g, %g; merging %g into %g\n", src->fPoint.fX, src->fPoint.fY,
         src->fID, dst->fID);
     dst->fAlpha = SkTMax(src->fAlpha, dst->fAlpha);
@@ -1059,12 +1062,18 @@
         set_top(edge, dst, nullptr, c);
         edge = next;
     }
-    list_remove<Vertex, &Vertex::fPrev, &Vertex::fNext>(src, head, nullptr);
+    mesh->remove(src);
 }
 
 uint8_t max_edge_alpha(Edge* a, Edge* b) {
-    return SkTMax(SkTMax(a->fTop->fAlpha, a->fBottom->fAlpha),
-                  SkTMax(b->fTop->fAlpha, b->fBottom->fAlpha));
+    if (a->fType == Edge::Type::kInner && b->fType == Edge::Type::kInner) {
+        return 255;
+    } else if (a->fType == Edge::Type::kOuter && b->fType == Edge::Type::kOuter) {
+        return 0;
+    } else {
+        return SkTMax(SkTMax(a->fTop->fAlpha, a->fBottom->fAlpha),
+                      SkTMax(b->fTop->fAlpha, b->fBottom->fAlpha));
+    }
 }
 
 Vertex* check_for_intersection(Edge* edge, Edge* other, EdgeList* activeEdges, Comparator& c,
@@ -1150,31 +1159,31 @@
     }
 }
 
-void merge_coincident_vertices(Vertex** vertices, Comparator& c, SkChunkAlloc& alloc) {
-    for (Vertex* v = (*vertices)->fNext; v != nullptr; v = v->fNext) {
+void merge_coincident_vertices(VertexList* mesh, Comparator& c, SkChunkAlloc& alloc) {
+    for (Vertex* v = mesh->fHead->fNext; v != nullptr; v = v->fNext) {
         if (c.sweep_lt(v->fPoint, v->fPrev->fPoint)) {
             v->fPoint = v->fPrev->fPoint;
         }
         if (coincident(v->fPrev->fPoint, v->fPoint)) {
-            merge_vertices(v->fPrev, v, vertices, c, alloc);
+            merge_vertices(v->fPrev, v, mesh, c, alloc);
         }
     }
 }
 
 // Stage 2: convert the contours to a mesh of edges connecting the vertices.
 
-Vertex* build_edges(Vertex** contours, int contourCnt, Comparator& c, SkChunkAlloc& alloc) {
-    Vertex* vertices = nullptr;
+void build_edges(Vertex** contours, int contourCnt, VertexList* mesh, Comparator& c,
+                 SkChunkAlloc& alloc) {
     Vertex* prev = nullptr;
     for (int i = 0; i < contourCnt; ++i) {
         for (Vertex* v = contours[i]; v != nullptr;) {
             Vertex* vNext = v->fNext;
-            connect(v->fPrev, v, alloc, c);
+            connect(v->fPrev, v, Edge::Type::kInner, c, alloc);
             if (prev) {
                 prev->fNext = v;
                 v->fPrev = prev;
             } else {
-                vertices = v;
+                mesh->fHead = v;
             }
             prev = v;
             v = vNext;
@@ -1182,24 +1191,23 @@
         }
     }
     if (prev) {
-        prev->fNext = vertices->fPrev = nullptr;
+        prev->fNext = mesh->fHead->fPrev = nullptr;
     }
-    return vertices;
+    mesh->fTail = prev;
 }
 
 // Stage 3: sort the vertices by increasing sweep direction.
 
-Vertex* sorted_merge(Vertex* a, Vertex* b, Comparator& c);
+void sorted_merge(Vertex* a, Vertex* b, VertexList* result, Comparator& c);
 
-void front_back_split(Vertex* v, Vertex** pFront, Vertex** pBack) {
+void front_back_split(VertexList* v, VertexList* front, VertexList* back) {
     Vertex* fast;
     Vertex* slow;
-    if (!v || !v->fNext) {
-        *pFront = v;
-        *pBack = nullptr;
+    if (!v->fHead || !v->fHead->fNext) {
+        *front = *v;
     } else {
-        slow = v;
-        fast = v->fNext;
+        slow = v->fHead;
+        fast = v->fHead->fNext;
 
         while (fast != nullptr) {
             fast = fast->fNext;
@@ -1208,32 +1216,33 @@
                 fast = fast->fNext;
             }
         }
-
-        *pFront = v;
-        *pBack = slow->fNext;
+        front->fHead = v->fHead;
+        front->fTail = slow;
+        back->fHead = slow->fNext;
+        back->fTail = v->fTail;
         slow->fNext->fPrev = nullptr;
         slow->fNext = nullptr;
     }
+    v->fHead = v->fTail = nullptr;
 }
 
-void merge_sort(Vertex** head, Comparator& c) {
-    if (!*head || !(*head)->fNext) {
+void merge_sort(VertexList* mesh, Comparator& c) {
+    if (!mesh->fHead || !mesh->fHead->fNext) {
         return;
     }
 
-    Vertex* a;
-    Vertex* b;
-    front_back_split(*head, &a, &b);
+    VertexList a;
+    VertexList b;
+    front_back_split(mesh, &a, &b);
 
     merge_sort(&a, c);
     merge_sort(&b, c);
 
-    *head = sorted_merge(a, b, c);
+    sorted_merge(a.fHead, b.fHead, mesh, c);
 }
 
-Vertex* sorted_merge(Vertex* a, Vertex* b, Comparator& c) {
+void sorted_merge(Vertex* a, Vertex* b, VertexList* result, Comparator& c) {
     VertexList vertices;
-
     while (a && b) {
         if (c.sweep_lt(a->fPoint, b->fPoint)) {
             Vertex* next = a->fNext;
@@ -1251,15 +1260,15 @@
     if (b) {
         vertices.insert(b, vertices.fTail, b->fNext);
     }
-    return vertices.fHead;
+    *result = vertices;
 }
 
 // Stage 4: Simplify the mesh by inserting new vertices at intersecting edges.
 
-void simplify(Vertex* vertices, Comparator& c, SkChunkAlloc& alloc) {
+void simplify(const VertexList& vertices, Comparator& c, SkChunkAlloc& alloc) {
     LOG("simplifying complex polygons\n");
     EdgeList activeEdges;
-    for (Vertex* v = vertices; v != nullptr; v = v->fNext) {
+    for (Vertex* v = vertices.fHead; v != nullptr; v = v->fNext) {
         if (!v->fFirstEdgeAbove && !v->fFirstEdgeBelow) {
             continue;
         }
@@ -1273,7 +1282,7 @@
             restartChecks = false;
             find_enclosing_edges(v, &activeEdges, &leftEnclosingEdge, &rightEnclosingEdge);
             if (v->fFirstEdgeBelow) {
-                for (Edge* edge = v->fFirstEdgeBelow; edge != nullptr; edge = edge->fNextEdgeBelow) {
+                for (Edge* edge = v->fFirstEdgeBelow; edge; edge = edge->fNextEdgeBelow) {
                     if (check_for_intersection(edge, leftEnclosingEdge, &activeEdges, c, alloc)) {
                         restartChecks = true;
                         break;
@@ -1295,8 +1304,8 @@
             }
         } while (restartChecks);
         if (v->fAlpha == 0) {
-            if ((leftEnclosingEdge && leftEnclosingEdge->fWinding < 0) &&
-                (rightEnclosingEdge && rightEnclosingEdge->fWinding > 0)) {
+            if ((leftEnclosingEdge && leftEnclosingEdge->fWinding > 0) &&
+                (rightEnclosingEdge && rightEnclosingEdge->fWinding < 0)) {
                 v->fAlpha = max_edge_alpha(leftEnclosingEdge, rightEnclosingEdge);
             }
         }
@@ -1314,11 +1323,11 @@
 
 // Stage 5: Tessellate the simplified mesh into monotone polygons.
 
-Poly* tessellate(Vertex* vertices, SkChunkAlloc& alloc) {
+Poly* tessellate(const VertexList& vertices, SkChunkAlloc& alloc) {
     LOG("tessellating simple polygons\n");
     EdgeList activeEdges;
     Poly* polys = nullptr;
-    for (Vertex* v = vertices; v != nullptr; v = v->fNext) {
+    for (Vertex* v = vertices.fHead; v != nullptr; v = v->fNext) {
         if (!v->fFirstEdgeAbove && !v->fFirstEdgeBelow) {
             continue;
         }
@@ -1391,7 +1400,8 @@
                             rightEnclosingEdge->fLeftPoly = rightPoly;
                         }
                     }
-                    Edge* join = ALLOC_NEW(Edge, (leftPoly->lastVertex(), v, 1), alloc);
+                    Edge* join = ALLOC_NEW(Edge,
+                        (leftPoly->lastVertex(), v, 1, Edge::Type::kInner), alloc);
                     leftPoly = leftPoly->addEdge(join, Poly::kRight_Side, alloc);
                     rightPoly = rightPoly->addEdge(join, Poly::kLeft_Side, alloc);
                 }
@@ -1433,19 +1443,17 @@
             apply_fill_type(fillType, edge->fRightPoly);
 }
 
-Vertex* remove_non_boundary_edges(Vertex* vertices, SkPath::FillType fillType,
-                                  SkChunkAlloc& alloc) {
-    for (Vertex* v = vertices; v != nullptr; v = v->fNext) {
+void remove_non_boundary_edges(const VertexList& mesh, SkPath::FillType fillType,
+                               SkChunkAlloc& alloc) {
+    for (Vertex* v = mesh.fHead; v != nullptr; v = v->fNext) {
         for (Edge* e = v->fFirstEdgeBelow; e != nullptr;) {
             Edge* next = e->fNextEdgeBelow;
             if (!is_boundary_edge(e, fillType)) {
-                remove_edge_above(e);
-                remove_edge_below(e);
+                disconnect(e);
             }
             e = next;
         }
     }
-    return vertices;
 }
 
 void get_edge_normal(const Edge* e, SkVector* normal) {
@@ -1469,7 +1477,7 @@
         get_edge_normal(e, &normal);
         float denom = 0.25f * static_cast<float>(e->fLine.magSq());
         if (prevNormal.dot(normal) < 0.0 && (dist * dist) <= denom) {
-            Edge* join = new_edge(prev, next, alloc, c);
+            Edge* join = new_edge(prev, next, Edge::Type::kInner, c, alloc);
             insert_edge(join, e, boundary);
             remove_edge(prevEdge, boundary);
             remove_edge(e, boundary);
@@ -1517,8 +1525,8 @@
             Vertex* innerVertex = ALLOC_NEW(Vertex, (innerPoint, 255), alloc);
             Vertex* outerVertex = ALLOC_NEW(Vertex, (outerPoint, 0), alloc);
             if (innerVertices.fTail && outerVertices.fTail) {
-                Edge innerEdge(innerVertices.fTail, innerVertex, 1);
-                Edge outerEdge(outerVertices.fTail, outerVertex, 1);
+                Edge innerEdge(innerVertices.fTail, innerVertex, 1, Edge::Type::kInner);
+                Edge outerEdge(outerVertices.fTail, outerVertex, 1, Edge::Type::kInner);
                 SkVector innerNormal;
                 get_edge_normal(&innerEdge, &innerNormal);
                 SkVector outerNormal;
@@ -1560,10 +1568,9 @@
         return;
     }
     do {
-        connect(outerVertex->fNext, outerVertex, alloc, c);
-        connect(innerVertex->fNext, innerVertex, alloc, c, 2);
-        connect(innerVertex, outerVertex->fNext, alloc, c, 2);
-        connect(outerVertex, innerVertex, alloc, c, 2);
+        connect(outerVertex->fPrev, outerVertex, Edge::Type::kOuter, c, alloc);
+        connect(innerVertex->fPrev, innerVertex, Edge::Type::kInner, c, alloc);
+        connect(outerVertex, innerVertex, Edge::Type::kConnector, c, alloc)->fWinding = 0;
         Vertex* innerNext = innerVertex->fNext;
         Vertex* outerNext = outerVertex->fNext;
         mesh->append(innerVertex);
@@ -1598,19 +1605,19 @@
                 down = true;
             }
         }
-        remove_edge_above(e);
-        remove_edge_below(e);
+        disconnect(e);
         e = next;
     }
 }
 
 // Stage 5b: Extract boundary edges.
 
-EdgeList* extract_boundaries(Vertex* vertices, SkPath::FillType fillType, SkChunkAlloc& alloc) {
+EdgeList* extract_boundaries(const VertexList& mesh, SkPath::FillType fillType,
+                             SkChunkAlloc& alloc) {
     LOG("extracting boundaries\n");
-    vertices = remove_non_boundary_edges(vertices, fillType, alloc);
+    remove_non_boundary_edges(mesh, fillType, alloc);
     EdgeList* boundaries = nullptr;
-    for (Vertex* v = vertices; v != nullptr; v = v->fNext) {
+    for (Vertex* v = mesh.fHead; v != nullptr; v = v->fNext) {
         while (v->fFirstEdgeBelow) {
             EdgeList* boundary = new_contour(&boundaries, alloc);
             extract_boundary(boundary, v->fFirstEdgeBelow, fillType, alloc);
@@ -1621,8 +1628,8 @@
 
 // This is a driver function which calls stages 2-5 in turn.
 
-Vertex* contours_to_mesh(Vertex** contours, int contourCnt, bool antialias,
-                         Comparator& c, SkChunkAlloc& alloc) {
+void contours_to_mesh(Vertex** contours, int contourCnt, bool antialias,
+                      VertexList* mesh, Comparator& c, SkChunkAlloc& alloc) {
 #if LOGGING_ENABLED
     for (int i = 0; i < contourCnt; ++i) {
         Vertex* v = contours[i];
@@ -1634,24 +1641,28 @@
     }
 #endif
     sanitize_contours(contours, contourCnt, antialias);
-    return build_edges(contours, contourCnt, c, alloc);
+    build_edges(contours, contourCnt, mesh, c, alloc);
 }
 
-Poly* mesh_to_polys(Vertex** vertices, Comparator& c, SkChunkAlloc& alloc) {
-    if (!vertices || !*vertices) {
-        return nullptr;
+void sort_and_simplify(VertexList* vertices, Comparator& c, SkChunkAlloc& alloc) {
+    if (!vertices || !vertices->fHead) {
+        return;
     }
 
     // Sort vertices in Y (secondarily in X).
     merge_sort(vertices, c);
     merge_coincident_vertices(vertices, c, alloc);
 #if LOGGING_ENABLED
-    for (Vertex* v = *vertices; v != nullptr; v = v->fNext) {
+    for (Vertex* v = vertices->fHead; v != nullptr; v = v->fNext) {
         static float gID = 0.0f;
         v->fID = gID++;
     }
 #endif
     simplify(*vertices, c, alloc);
+}
+
+Poly* mesh_to_polys(VertexList* vertices, Comparator& c, SkChunkAlloc& alloc) {
+    sort_and_simplify(vertices, c, alloc);
     return tessellate(*vertices, alloc);
 }
 
@@ -1666,7 +1677,8 @@
         c.sweep_lt = sweep_lt_vert;
         c.sweep_gt = sweep_gt_vert;
     }
-    Vertex* mesh = contours_to_mesh(contours, contourCnt, antialias, c, alloc);
+    VertexList mesh;
+    contours_to_mesh(contours, contourCnt, antialias, &mesh, c, alloc);
     Poly* polys = mesh_to_polys(&mesh, c, alloc);
     if (antialias) {
         EdgeList* boundaries = extract_boundaries(mesh, fillType, alloc);
@@ -1677,7 +1689,8 @@
                 boundary_to_aa_mesh(boundary, &aaMesh, c, alloc);
             }
         }
-        return mesh_to_polys(&aaMesh.fHead, c, alloc);
+        sort_and_simplify(&aaMesh, c, alloc);
+        return tessellate(aaMesh, alloc);
     }
     return polys;
 }
diff --git a/src/gpu/GrTextureMaker.cpp b/src/gpu/GrTextureMaker.cpp
index 7123a66..37272be 100644
--- a/src/gpu/GrTextureMaker.cpp
+++ b/src/gpu/GrTextureMaker.cpp
@@ -84,8 +84,8 @@
     SkRect domain;
     DomainMode domainMode =
         DetermineDomainMode(constraintRect, filterConstraint, coordsLimitedToConstraintRect,
-                            texture->width(), texture->height(), nullptr, fmForDetermineDomain,
-                            &domain);
+                            texture->width(), texture->height(),
+                            nullptr, fmForDetermineDomain, &domain);
     SkASSERT(kTightCopy_DomainMode != domainMode);
     SkMatrix normalizedTextureMatrix = textureMatrix;
     normalizedTextureMatrix.postIDiv(texture->width(), texture->height());
diff --git a/src/gpu/GrTextureOpList.cpp b/src/gpu/GrTextureOpList.cpp
index b17de3f..61990de 100644
--- a/src/gpu/GrTextureOpList.cpp
+++ b/src/gpu/GrTextureOpList.cpp
@@ -10,7 +10,7 @@
 #include "GrAuditTrail.h"
 #include "GrGpu.h"
 #include "GrTextureProxy.h"
-
+#include "SkStringUtils.h"
 #include "ops/GrCopySurfaceOp.h"
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -82,7 +82,7 @@
                                   GrSurface* src,
                                   const SkIRect& srcRect,
                                   const SkIPoint& dstPoint) {
-    sk_sp<GrOp> op = GrCopySurfaceOp::Make(dst, src, srcRect, dstPoint);
+    std::unique_ptr<GrOp> op = GrCopySurfaceOp::Make(dst, src, srcRect, dstPoint);
     if (!op) {
         return false;
     }
@@ -90,15 +90,16 @@
     this->addDependency(src);
 #endif
 
-    this->recordOp(std::move(op));
+    // See the comment in GrRenderTargetOpList about why we pass the invalid ID here.
+    this->recordOp(std::move(op), GrGpuResource::UniqueID::InvalidID());
     return true;
 }
 
-void GrTextureOpList::recordOp(sk_sp<GrOp> op) {
+void GrTextureOpList::recordOp(std::unique_ptr<GrOp> op, GrGpuResource::UniqueID renderTargetID) {
     // A closed GrOpList should never receive new/more ops
     SkASSERT(!this->isClosed());
 
-    GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get());
+    GR_AUDIT_TRAIL_ADD_OP(fAuditTrail, op.get(), renderTargetID);
     GrOP_INFO("Re-Recording (%s, B%u)\n"
         "\tBounds LRTB (%f, %f, %f, %f)\n",
         op->name(),
diff --git a/src/gpu/GrTextureOpList.h b/src/gpu/GrTextureOpList.h
index 367fa0c..325ab5d 100644
--- a/src/gpu/GrTextureOpList.h
+++ b/src/gpu/GrTextureOpList.h
@@ -8,6 +8,7 @@
 #ifndef GrTexureOpList_DEFINED
 #define GrTexureOpList_DEFINED
 
+#include "GrGpuResource.h"
 #include "GrOpList.h"
 
 #include "SkTArray.h"
@@ -26,7 +27,7 @@
     ~GrTextureOpList() override;
 
     /**
-     * Empties the draw buffer of any queued up draws.
+     * Empties the draw buffer of any queued ops.
      */
     void reset() override;
 
@@ -34,8 +35,8 @@
     void freeGpuResources() override {}
 
     /**
-     * Together these two functions flush all queued up draws to GrCommandBuffer. The return value
-     * of drawOps() indicates whether any commands were actually issued to the GPU.
+     * Together these two functions flush all queued ops to GrGpuCommandBuffer. The return value
+     * of executeOps() indicates whether any commands were actually issued to the GPU.
      */
     void prepareOps(GrOpFlushState* flushState) override;
     bool executeOps(GrOpFlushState* flushState) override;
@@ -60,9 +61,10 @@
     SkDEBUGCODE(void dump() const override;)
 
 private:
-    void recordOp(sk_sp<GrOp>);
+    // The unique ID is only needed for the audit trail. This should be removed with MDB.
+    void recordOp(std::unique_ptr<GrOp>, GrGpuResource::UniqueID renderTargetID);
 
-    SkSTArray<2, sk_sp<GrOp>, true> fRecordedOps;
+    SkSTArray<2, std::unique_ptr<GrOp>, true> fRecordedOps;
     GrGpu*                          fGpu;
 
     typedef GrOpList INHERITED;
diff --git a/src/gpu/GrTextureProducer.cpp b/src/gpu/GrTextureProducer.cpp
index 8b64fea..0adae8c 100644
--- a/src/gpu/GrTextureProducer.cpp
+++ b/src/gpu/GrTextureProducer.cpp
@@ -30,20 +30,13 @@
     GrPaint paint;
     paint.setGammaCorrect(true);
 
-    SkScalar sx SK_INIT_TO_AVOID_WARNING;
-    SkScalar sy SK_INIT_TO_AVOID_WARNING;
-    if (subset) {
-        sx = 1.f / inputTexture->width();
-        sy = 1.f / inputTexture->height();
-    }
-
     if (copyParams.fFilter != GrSamplerParams::kNone_FilterMode && subset &&
         (subset->width() != copyParams.fWidth || subset->height() != copyParams.fHeight)) {
         SkRect domain;
-        domain.fLeft = (subset->fLeft + 0.5f) * sx;
-        domain.fTop = (subset->fTop + 0.5f)* sy;
-        domain.fRight = (subset->fRight - 0.5f) * sx;
-        domain.fBottom = (subset->fBottom - 0.5f) * sy;
+        domain.fLeft = subset->fLeft + 0.5f;
+        domain.fTop = subset->fTop + 0.5f;
+        domain.fRight = subset->fRight - 0.5f;
+        domain.fBottom = subset->fBottom - 0.5f;
         // This would cause us to read values from outside the subset. Surely, the caller knows
         // better!
         SkASSERT(copyParams.fFilter != GrSamplerParams::kMipMap_FilterMode);
@@ -59,6 +52,9 @@
 
     SkRect localRect;
     if (subset) {
+        SkScalar sx = SK_Scalar1 / inputTexture->width();
+        SkScalar sy = SK_Scalar1 / inputTexture->height();
+
         localRect = SkRect::Make(*subset);
         localRect.fLeft *= sx;
         localRect.fTop *= sy;
@@ -69,7 +65,8 @@
     }
 
     SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight);
-    copyRTC->fillRectToRect(GrNoClip(), paint, GrAA::kNo, SkMatrix::I(), dstRect, localRect);
+    copyRTC->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
+                            localRect);
     return copyRTC->asTexture().release();
 }
 
@@ -149,10 +146,7 @@
     // Unless we know the amount of outset and the texture matrix we have to conservatively enforce
     // the domain.
     if (restrictFilterToRect) {
-        domainRect->fLeft = constraintRect.fLeft + kDomainInset;
-        domainRect->fTop = constraintRect.fTop + kDomainInset;
-        domainRect->fRight = constraintRect.fRight - kDomainInset;
-        domainRect->fBottom = constraintRect.fBottom - kDomainInset;
+        *domainRect = constraintRect.makeInset(kDomainInset, kDomainInset);
     } else if (textureContentArea) {
         // If we got here then: there is a textureContentArea, the coords are limited to the
         // constraint rect, and we're allowed to filter across the constraint rect boundary. So
@@ -213,10 +207,6 @@
     if (domainRect->fTop > domainRect->fBottom) {
         domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom);
     }
-    domainRect->fLeft /= texW;
-    domainRect->fTop /= texH;
-    domainRect->fRight /= texW;
-    domainRect->fBottom /= texH;
     return kDomain_DomainMode;
 }
 
diff --git a/src/gpu/GrTextureToYUVPlanes.cpp b/src/gpu/GrTextureToYUVPlanes.cpp
index 6268bef..7102945 100644
--- a/src/gpu/GrTextureToYUVPlanes.cpp
+++ b/src/gpu/GrTextureToYUVPlanes.cpp
@@ -43,7 +43,8 @@
     GrPaint paint;
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
     paint.addColorFragmentProcessor(std::move(fp));
-    dst->drawRect(GrNoClip(), paint, GrAA::kNo, SkMatrix::I(), SkRect::MakeIWH(dstW, dstH));
+    dst->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
+                  SkRect::MakeIWH(dstW, dstH));
     return true;
 }
 
diff --git a/src/gpu/GrXferProcessor.cpp b/src/gpu/GrXferProcessor.cpp
index 13533af..c2a16b8 100644
--- a/src/gpu/GrXferProcessor.cpp
+++ b/src/gpu/GrXferProcessor.cpp
@@ -37,14 +37,6 @@
                                                             const GrCaps& caps) const {
     GrXferProcessor::OptFlags flags =
             this->onGetOptimizations(analysis, doesStencilWrite, overrideColor, caps);
-
-    if (this->willReadDstColor()) {
-        // When performing a dst read we handle coverage in the base class.
-        SkASSERT(!(flags & GrXferProcessor::kIgnoreCoverage_OptFlag));
-        if (analysis.fCoveragePOI.isSolidWhite()) {
-            flags |= GrXferProcessor::kIgnoreCoverage_OptFlag;
-        }
-    }
     return flags;
 }
 
diff --git a/src/gpu/GrYUVProvider.cpp b/src/gpu/GrYUVProvider.cpp
index 6fbad18..1330cfe 100644
--- a/src/gpu/GrYUVProvider.cpp
+++ b/src/gpu/GrYUVProvider.cpp
@@ -11,6 +11,7 @@
 #include "effects/GrGammaEffect.h"
 #include "effects/GrYUVEffect.h"
 
+#include "SkAutoMalloc.h"
 #include "SkCachedData.h"
 #include "SkRefCnt.h"
 #include "SkResourceCache.h"
@@ -147,7 +148,7 @@
     const SkRect r = SkRect::MakeIWH(yuvInfo.fSizeInfo.fSizes[SkYUVSizeInfo::kY].fWidth,
             yuvInfo.fSizeInfo.fSizes[SkYUVSizeInfo::kY].fHeight);
 
-    renderTargetContext->drawRect(GrNoClip(), paint, GrAA::kNo, SkMatrix::I(), r);
+    renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), r);
 
     return renderTargetContext->asTexture();
 }
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index b082241..845b22c 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -163,21 +163,10 @@
         return nullptr;
     }
 
-    SkColorType ct = origInfo.colorType();
-    SkAlphaType at = origInfo.alphaType();
-    SkColorSpace* cs = origInfo.colorSpace();
-    if (kRGB_565_SkColorType == ct || kGray_8_SkColorType == ct) {
-        at = kOpaque_SkAlphaType;  // force this setting
-    }
-    if (kOpaque_SkAlphaType != at) {
-        at = kPremul_SkAlphaType;  // force this setting
-    }
-
-    GrPixelConfig config = SkImageInfo2GrPixelConfig(ct, at, cs, *context->caps());
-
+    GrPixelConfig config = SkImageInfo2GrPixelConfig(origInfo, *context->caps());
     return context->makeRenderTargetContext(SkBackingFit::kExact,               // Why exact?
                                     origInfo.width(), origInfo.height(),
-                                    config, sk_ref_sp(cs), sampleCount,
+                                    config, sk_ref_sp(origInfo.colorSpace()), sampleCount,
                                     origin, surfaceProps, budgeted);
 }
 
@@ -280,7 +269,7 @@
         return;
     }
 
-    fRenderTargetContext->drawPaint(fClip, grPaint, *draw.fMatrix);
+    fRenderTargetContext->drawPaint(fClip, std::move(grPaint), *draw.fMatrix);
 }
 
 // must be in SkCanvas::PointMode order
@@ -339,7 +328,7 @@
         path.setIsVolatile(true);
         path.moveTo(pts[0]);
         path.lineTo(pts[1]);
-        fRenderTargetContext->drawPath(fClip, grPaint, GrBoolToAA(paint.isAntiAlias()),
+        fRenderTargetContext->drawPath(fClip, std::move(grPaint), GrBoolToAA(paint.isAntiAlias()),
                                        *draw.fMatrix, path, style);
         return;
     }
@@ -378,7 +367,7 @@
     }
 
     fRenderTargetContext->drawVertices(fClip,
-                                       grPaint,
+                                       std::move(grPaint),
                                        *viewMatrix,
                                        primitiveType,
                                        SkToS32(count),
@@ -416,8 +405,8 @@
     }
 
     GrStyle style(paint);
-    fRenderTargetContext->drawRect(fClip, grPaint, GrBoolToAA(paint.isAntiAlias()), *draw.fMatrix,
-                                   rect, &style);
+    fRenderTargetContext->drawRect(fClip, std::move(grPaint), GrBoolToAA(paint.isAntiAlias()),
+                                   *draw.fMatrix, rect, &style);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -455,7 +444,7 @@
                         return;
                     }
                     if (mf->directFilterRRectMaskGPU(fContext.get(), fRenderTargetContext.get(),
-                                                     &grPaint, fClip, *draw.fMatrix,
+                                                     std::move(grPaint), fClip, *draw.fMatrix,
                                                      style.strokeRec(), rrect, devRRect)) {
                         return;
                     }
@@ -481,8 +470,8 @@
 
     SkASSERT(!style.pathEffect());
 
-    fRenderTargetContext->drawRRect(fClip, grPaint, GrBoolToAA(paint.isAntiAlias()), *draw.fMatrix,
-                                    rrect, style);
+    fRenderTargetContext->drawRRect(fClip, std::move(grPaint), GrBoolToAA(paint.isAntiAlias()),
+                                    *draw.fMatrix, rrect, style);
 }
 
 
@@ -509,7 +498,7 @@
             return;
         }
 
-        fRenderTargetContext->drawDRRect(fClip, grPaint, GrBoolToAA(paint.isAntiAlias()),
+        fRenderTargetContext->drawDRRect(fClip, std::move(grPaint), GrBoolToAA(paint.isAntiAlias()),
                                          *draw.fMatrix, outer, inner);
         return;
     }
@@ -542,8 +531,8 @@
         return;
     }
 
-    fRenderTargetContext->drawRegion(fClip, grPaint, GrBoolToAA(paint.isAntiAlias()), *draw.fMatrix,
-                                     region, GrStyle(paint));
+    fRenderTargetContext->drawRegion(fClip, std::move(grPaint), GrBoolToAA(paint.isAntiAlias()),
+                                     *draw.fMatrix, region, GrStyle(paint));
 }
 
 void SkGpuDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) {
@@ -572,8 +561,8 @@
         return;
     }
 
-    fRenderTargetContext->drawOval(fClip, grPaint, GrBoolToAA(paint.isAntiAlias()), *draw.fMatrix,
-                                   oval, GrStyle(paint));
+    fRenderTargetContext->drawOval(fClip, std::move(grPaint), GrBoolToAA(paint.isAntiAlias()),
+                                   *draw.fMatrix, oval, GrStyle(paint));
 }
 
 void SkGpuDevice::drawArc(const SkDraw& draw, const SkRect& oval, SkScalar startAngle,
@@ -592,8 +581,9 @@
         return;
     }
 
-    fRenderTargetContext->drawArc(fClip, grPaint, GrBoolToAA(paint.isAntiAlias()), *draw.fMatrix,
-                                  oval, startAngle, sweepAngle, useCenter, GrStyle(paint));
+    fRenderTargetContext->drawArc(fClip, std::move(grPaint), GrBoolToAA(paint.isAntiAlias()),
+                                  *draw.fMatrix, oval, startAngle, sweepAngle, useCenter,
+                                  GrStyle(paint));
 }
 
 #include "SkMaskFilter.h"
@@ -649,9 +639,8 @@
         return;
     }
 
-    fRenderTargetContext->fillRectWithLocalMatrix(fClip, grPaint,
-                                                  GrBoolToAA(newPaint.isAntiAlias()), m, rect,
-                                                  local);
+    fRenderTargetContext->fillRectWithLocalMatrix(
+            fClip, std::move(grPaint), GrBoolToAA(newPaint.isAntiAlias()), m, rect, local);
 }
 
 void SkGpuDevice::drawPath(const SkDraw& draw, const SkPath& origSrcPath,
@@ -1066,16 +1055,16 @@
         // Use a constrained texture domain to avoid color bleeding
         SkRect domain;
         if (srcRect.width() > SK_Scalar1) {
-            domain.fLeft  = (srcRect.fLeft + 0.5f) * iw;
-            domain.fRight = (srcRect.fRight - 0.5f) * iw;
+            domain.fLeft  = srcRect.fLeft + 0.5f;
+            domain.fRight = srcRect.fRight - 0.5f;
         } else {
-            domain.fLeft = domain.fRight = srcRect.centerX() * iw;
+            domain.fLeft = domain.fRight = srcRect.centerX();
         }
         if (srcRect.height() > SK_Scalar1) {
-            domain.fTop  = (srcRect.fTop + 0.5f) * ih;
-            domain.fBottom = (srcRect.fBottom - 0.5f) * ih;
+            domain.fTop  = srcRect.fTop + 0.5f;
+            domain.fBottom = srcRect.fBottom - 0.5f;
         } else {
-            domain.fTop = domain.fBottom = srcRect.centerY() * ih;
+            domain.fTop = domain.fBottom = srcRect.centerY();
         }
         if (bicubic) {
             fp = GrBicubicEffect::Make(texture.get(), std::move(colorSpaceXform), texMatrix,
@@ -1103,7 +1092,7 @@
     // Coverage-based AA would cause seams between tiles.
     GrAA aa = GrBoolToAA(paint.isAntiAlias() &&
                          fRenderTargetContext->isStencilBufferMultisampled());
-    fRenderTargetContext->drawRect(fClip, grPaint, aa, viewMatrix, dstRect);
+    fRenderTargetContext->drawRect(fClip, std::move(grPaint), aa, viewMatrix, dstRect);
 }
 
 void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
@@ -1195,19 +1184,17 @@
 
     const SkIRect& subset = result->subset();
 
-    fRenderTargetContext->fillRectToRect(fClip,
-                                         grPaint,
-                                         GrBoolToAA(paint.isAntiAlias()),
-                                         SkMatrix::I(),
-                                         SkRect::Make(SkIRect::MakeXYWH(left + offset.fX,
-                                                                        top + offset.fY,
-                                                                        subset.width(),
-                                                                        subset.height())),
-                                        SkRect::MakeXYWH(
-                                            SkIntToScalar(subset.fLeft) / texture->width(),
-                                            SkIntToScalar(subset.fTop) / texture->height(),
-                                            SkIntToScalar(subset.width()) / texture->width(),
-                                            SkIntToScalar(subset.height()) / texture->height()));
+    fRenderTargetContext->fillRectToRect(
+            fClip,
+            std::move(grPaint),
+            GrBoolToAA(paint.isAntiAlias()),
+            SkMatrix::I(),
+            SkRect::Make(SkIRect::MakeXYWH(
+                    left + offset.fX, top + offset.fY, subset.width(), subset.height())),
+            SkRect::MakeXYWH(SkIntToScalar(subset.fLeft) / texture->width(),
+                             SkIntToScalar(subset.fTop) / texture->height(),
+                             SkIntToScalar(subset.width()) / texture->width(),
+                             SkIntToScalar(subset.height()) / texture->height()));
 }
 
 void SkGpuDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap,
@@ -1476,8 +1463,9 @@
 
     std::unique_ptr<SkLatticeIter> iter(
             new SkLatticeIter(producer->width(), producer->height(), center, dst));
-    fRenderTargetContext->drawImageLattice(fClip, grPaint, *draw.fMatrix, producer->width(),
-                                           producer->height(), std::move(iter), dst);
+    fRenderTargetContext->drawImageLattice(fClip, std::move(grPaint), *draw.fMatrix,
+                                           producer->width(), producer->height(), std::move(iter),
+                                           dst);
 }
 
 void SkGpuDevice::drawImageNine(const SkDraw& draw, const SkImage* image,
@@ -1529,8 +1517,9 @@
 
     std::unique_ptr<SkLatticeIter> iter(
             new SkLatticeIter(lattice, dst));
-    fRenderTargetContext->drawImageLattice(fClip, grPaint, *draw.fMatrix, producer->width(),
-                                           producer->height(), std::move(iter), dst);
+    fRenderTargetContext->drawImageLattice(fClip, std::move(grPaint), *draw.fMatrix,
+                                           producer->width(), producer->height(), std::move(iter),
+                                           dst);
 }
 
 void SkGpuDevice::drawImageLattice(const SkDraw& draw, const SkImage* image,
@@ -1627,7 +1616,7 @@
             i += 6;
         }
         fRenderTargetContext->drawVertices(fClip,
-                                           grPaint,
+                                           std::move(grPaint),
                                            *draw.fMatrix,
                                            kLines_GrPrimitiveType,
                                            vertexCount,
@@ -1684,7 +1673,7 @@
     }
 
     fRenderTargetContext->drawVertices(fClip,
-                                       grPaint,
+                                       std::move(grPaint),
                                        *draw.fMatrix,
                                        primType,
                                        vertexCount,
@@ -1726,7 +1715,8 @@
     }
 
     SkDEBUGCODE(this->validate();)
-    fRenderTargetContext->drawAtlas(fClip, grPaint, *draw.fMatrix, count, xform, texRect, colors);
+    fRenderTargetContext->drawAtlas(fClip, std::move(grPaint), *draw.fMatrix, count, xform, texRect,
+                                    colors);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1746,8 +1736,8 @@
 
     SkDEBUGCODE(this->validate();)
 
-    fRenderTargetContext->drawText(fClip, grPaint, paint, *draw.fMatrix,
-                                   (const char *)text, byteLength, x, y, draw.fRC->getBounds());
+    fRenderTargetContext->drawText(fClip, std::move(grPaint), paint, *draw.fMatrix,
+                                   (const char*)text, byteLength, x, y, draw.fRC->getBounds());
 }
 
 void SkGpuDevice::drawPosText(const SkDraw& draw, const void* text, size_t byteLength,
@@ -1765,8 +1755,8 @@
 
     SkDEBUGCODE(this->validate();)
 
-    fRenderTargetContext->drawPosText(fClip, grPaint, paint, *draw.fMatrix,
-                                      (const char *)text, byteLength, pos, scalarsPerPos, offset,
+    fRenderTargetContext->drawPosText(fClip, std::move(grPaint), paint, *draw.fMatrix,
+                                      (const char*)text, byteLength, pos, scalarsPerPos, offset,
                                       draw.fRC->getBounds());
 }
 
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 9556146..cff26b4 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -215,13 +215,13 @@
     }
     GrAA aa = GrBoolToAA(paint.isAntiAlias());
     if (canUseTextureCoordsAsLocalCoords) {
-        fRenderTargetContext->fillRectToRect(clip, grPaint, aa, viewMatrix, clippedDstRect,
-                                             clippedSrcRect);
+        fRenderTargetContext->fillRectToRect(clip, std::move(grPaint), aa, viewMatrix,
+                                             clippedDstRect, clippedSrcRect);
         return;
     }
 
     if (!mf) {
-        fRenderTargetContext->drawRect(clip, grPaint, aa, viewMatrix, clippedDstRect);
+        fRenderTargetContext->drawRect(clip, std::move(grPaint), aa, viewMatrix, clippedDstRect);
         return;
     }
 
@@ -233,7 +233,7 @@
         SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
         if (mf->directFilterRRectMaskGPU(fContext.get(),
                                          fRenderTargetContext.get(),
-                                         &grPaint,
+                                         std::move(grPaint),
                                          clip,
                                          viewMatrix,
                                          rec,
@@ -247,6 +247,6 @@
     rectPath.addRect(clippedDstRect);
     rectPath.setIsVolatile(true);
     GrBlurUtils::drawPathWithMaskFilter(this->context(), fRenderTargetContext.get(), fClip,
-                                        rectPath, &grPaint, aa, viewMatrix, mf,
+                                        rectPath, std::move(grPaint), aa, viewMatrix, mf,
                                         GrStyle::SimpleFill(), true);
 }
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index 4147855..5c937d4 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-
 #include "SkGr.h"
 #include "SkGrPriv.h"
 
@@ -19,16 +18,17 @@
 #include "GrXferProcessor.h"
 #include "GrYUVProvider.h"
 
+#include "SkAutoMalloc.h"
 #include "SkBlendModePriv.h"
+#include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkConfig8888.h"
-#include "SkCanvas.h"
 #include "SkData.h"
 #include "SkMaskFilter.h"
 #include "SkMessageBus.h"
 #include "SkMipMap.h"
-#include "SkPixelRef.h"
 #include "SkPM4fPriv.h"
+#include "SkPixelRef.h"
 #include "SkResourceCache.h"
 #include "SkTemplates.h"
 #include "SkYUVPlanesCache.h"
@@ -112,58 +112,6 @@
 //////////////////////////////////////////////////////////////////////////////
 
 /**
- * Fill out buffer with the compressed format Ganesh expects from a colortable
- * based bitmap. [palette (colortable) + indices].
- *
- * At the moment Ganesh only supports 8bit version. If Ganesh allowed we others
- * we could detect that the colortable.count is <= 16, and then repack the
- * indices as nibbles to save RAM, but it would take more time (i.e. a lot
- * slower than memcpy), so skipping that for now.
- *
- * Ganesh wants a full 256 palette entry, even though Skia's ctable is only as big
- * as the colortable.count says it is.
- */
-static void build_index8_data(void* buffer, const SkPixmap& pixmap) {
-    SkASSERT(kIndex_8_SkColorType == pixmap.colorType());
-
-    const SkColorTable* ctable = pixmap.ctable();
-    char* dst = (char*)buffer;
-
-    const int count = ctable->count();
-
-    SkDstPixelInfo dstPI;
-    dstPI.fColorType = kRGBA_8888_SkColorType;
-    dstPI.fAlphaType = kPremul_SkAlphaType;
-    dstPI.fPixels = buffer;
-    dstPI.fRowBytes = count * sizeof(SkPMColor);
-
-    SkSrcPixelInfo srcPI;
-    srcPI.fColorType = kN32_SkColorType;
-    srcPI.fAlphaType = kPremul_SkAlphaType;
-    srcPI.fPixels = ctable->readColors();
-    srcPI.fRowBytes = count * sizeof(SkPMColor);
-
-    srcPI.convertPixelsTo(&dstPI, count, 1);
-
-    // always skip a full 256 number of entries, even if we memcpy'd fewer
-    dst += 256 * sizeof(GrColor);
-
-    if ((unsigned)pixmap.width() == pixmap.rowBytes()) {
-        memcpy(dst, pixmap.addr(), pixmap.getSafeSize());
-    } else {
-        // need to trim off the extra bytes per row
-        size_t width = pixmap.width();
-        size_t rowBytes = pixmap.rowBytes();
-        const uint8_t* src = pixmap.addr8();
-        for (int y = 0; y < pixmap.height(); y++) {
-            memcpy(dst, src, width);
-            src += rowBytes;
-            dst += width;
-        }
-    }
-}
-
-/**
  *  Once we have made SkImages handle all lazy/deferred/generated content, the YUV apis will
  *  be gone from SkPixelRef, and we can remove this subclass entirely.
  */
@@ -224,6 +172,9 @@
     const GrCaps* caps = ctx->caps();
     GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(pixmap.info(), *caps);
 
+    // TODO: We're checking for srgbSupport, but we can then end up picking sBGRA as our pixel
+    // config (which may not be supported). We need better fallback management here.
+
     if (caps->srgbSupport() &&
         pixmap.info().colorSpace() && pixmap.info().colorSpace()->gammaCloseToSRGB() &&
         !(GrPixelConfigIsSRGB(desc.fConfig) ||
@@ -253,15 +204,8 @@
         pmap = &tmpPixmap;
         // must rebuild desc, since we've forced the info to be N32
         desc = GrImageInfoToSurfaceDesc(pmap->info(), *caps);
-    } else if (kGray_8_SkColorType == pixmap.colorType()) {
-        // We don't have Gray8 support as a pixel config, so expand to 8888
-
-        // We should have converted sRGB Gray8 above (if we have sRGB support):
-        SkASSERT(!caps->srgbSupport() || !pixmap.info().colorSpace() ||
-                 !pixmap.info().colorSpace()->gammaCloseToSRGB());
-
-        SkImageInfo info = SkImageInfo::MakeN32(pixmap.width(), pixmap.height(),
-                                                kOpaque_SkAlphaType);
+    } else if (kIndex_8_SkColorType == pixmap.colorType()) {
+        SkImageInfo info = SkImageInfo::MakeN32Premul(pixmap.width(), pixmap.height());
         tmpBitmap.allocPixels(info);
         if (!pixmap.readPixels(info, tmpBitmap.getPixels(), tmpBitmap.rowBytes())) {
             return nullptr;
@@ -272,30 +216,6 @@
         pmap = &tmpPixmap;
         // must rebuild desc, since we've forced the info to be N32
         desc = GrImageInfoToSurfaceDesc(pmap->info(), *caps);
-    } else if (kIndex_8_SkColorType == pixmap.colorType()) {
-        if (caps->isConfigTexturable(kIndex_8_GrPixelConfig)) {
-            size_t imageSize = GrCompressedFormatDataSize(kIndex_8_GrPixelConfig,
-                                                          pixmap.width(), pixmap.height());
-            SkAutoMalloc storage(imageSize);
-            build_index8_data(storage.get(), pixmap);
-
-            // our compressed data will be trimmed, so pass width() for its
-            // "rowBytes", since they are the same now.
-            return ctx->textureProvider()->createTexture(desc, budgeted, storage.get(),
-                                                         pixmap.width());
-        } else {
-            SkImageInfo info = SkImageInfo::MakeN32Premul(pixmap.width(), pixmap.height());
-            tmpBitmap.allocPixels(info);
-            if (!pixmap.readPixels(info, tmpBitmap.getPixels(), tmpBitmap.rowBytes())) {
-                return nullptr;
-            }
-            if (!tmpBitmap.peekPixels(&tmpPixmap)) {
-                return nullptr;
-            }
-            pmap = &tmpPixmap;
-            // must rebuild desc, since we've forced the info to be N32
-            desc = GrImageInfoToSurfaceDesc(pmap->info(), *caps);
-        }
     }
 
     return ctx->textureProvider()->createTexture(desc, budgeted, pmap->addr(),
@@ -458,13 +378,11 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-// alphatype is ignore for now, but if GrPixelConfig is expanded to encompass
-// alpha info, that will be considered.
-GrPixelConfig SkImageInfo2GrPixelConfig(SkColorType ct, SkAlphaType, const SkColorSpace* cs,
-                                        const GrCaps& caps) {
+GrPixelConfig SkImageInfo2GrPixelConfig(const SkImageInfo& info, const GrCaps& caps) {
     // We intentionally ignore profile type for non-8888 formats. Anything we can't support
     // in hardware will be expanded to sRGB 8888 in GrUploadPixmapToTexture.
-    switch (ct) {
+    SkColorSpace* cs = info.colorSpace();
+    switch (info.colorType()) {
         case kUnknown_SkColorType:
             return kUnknown_GrPixelConfig;
         case kAlpha_8_SkColorType:
@@ -480,9 +398,9 @@
             return (caps.srgbSupport() && cs && cs->gammaCloseToSRGB())
                    ? kSBGRA_8888_GrPixelConfig : kBGRA_8888_GrPixelConfig;
         case kIndex_8_SkColorType:
-            return kIndex_8_GrPixelConfig;
+            return kSkia8888_GrPixelConfig;
         case kGray_8_SkColorType:
-            return kAlpha_8_GrPixelConfig; // TODO: gray8 support on gpu
+            return kGray_8_GrPixelConfig;
         case kRGBA_F16_SkColorType:
             return kRGBA_half_GrPixelConfig;
     }
@@ -496,8 +414,8 @@
         case kAlpha_8_GrPixelConfig:
             ct = kAlpha_8_SkColorType;
             break;
-        case kIndex_8_GrPixelConfig:
-            ct = kIndex_8_SkColorType;
+        case kGray_8_GrPixelConfig:
+            ct = kGray_8_SkColorType;
             break;
         case kRGB_565_GrPixelConfig:
             ct = kRGB_565_SkColorType;
diff --git a/src/gpu/effects/GrBezierEffect.cpp b/src/gpu/effects/GrBezierEffect.cpp
index 8db60ac..cb0e0c5 100644
--- a/src/gpu/effects/GrBezierEffect.cpp
+++ b/src/gpu/effects/GrBezierEffect.cpp
@@ -84,9 +84,7 @@
 
     GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
-    if (!gp.colorIgnored()) {
-        this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
-    }
+    this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
     // Setup position
     this->setupPosition(vertBuilder,
@@ -222,7 +220,6 @@
                              GrProcessorKeyBuilder* b) {
     const GrConicEffect& ce = gp.cast<GrConicEffect>();
     uint32_t key = ce.isAntiAliased() ? (ce.isFilled() ? 0x0 : 0x1) : 0x2;
-    key |= GrColor_ILLEGAL != ce.color() ? 0x4 : 0x0;
     key |= 0xff != ce.coverageScale() ? 0x8 : 0x0;
     key |= ce.usesLocalCoords() && ce.localMatrix().hasPerspective() ? 0x10 : 0x0;
     key |= ComputePosKey(ce.viewMatrix()) << 5;
@@ -346,9 +343,7 @@
 
     GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
-    if (!gp.colorIgnored()) {
-        this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
-    }
+    this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
     // Setup position
     this->setupPosition(vertBuilder,
@@ -426,7 +421,6 @@
                             GrProcessorKeyBuilder* b) {
     const GrQuadEffect& ce = gp.cast<GrQuadEffect>();
     uint32_t key = ce.isAntiAliased() ? (ce.isFilled() ? 0x0 : 0x1) : 0x2;
-    key |= ce.color() != GrColor_ILLEGAL ? 0x4 : 0x0;
     key |= ce.coverageScale() != 0xff ? 0x8 : 0x0;
     key |= ce.usesLocalCoords() && ce.localMatrix().hasPerspective() ? 0x10 : 0x0;
     key |= ComputePosKey(ce.viewMatrix()) << 5;
@@ -656,7 +650,6 @@
                              GrProcessorKeyBuilder* b) {
     const GrCubicEffect& ce = gp.cast<GrCubicEffect>();
     uint32_t key = ce.isAntiAliased() ? (ce.isFilled() ? 0x0 : 0x1) : 0x2;
-    key |= ce.color() != GrColor_ILLEGAL ? 0x4 : 0x8;
     key |= ComputePosKey(ce.viewMatrix()) << 5;
     b->add32(key);
 }
diff --git a/src/gpu/effects/GrBezierEffect.h b/src/gpu/effects/GrBezierEffect.h
index edb1408..824883c 100644
--- a/src/gpu/effects/GrBezierEffect.h
+++ b/src/gpu/effects/GrBezierEffect.h
@@ -100,7 +100,6 @@
     inline bool isFilled() const { return GrProcessorEdgeTypeIsFill(fEdgeType); }
     inline GrPrimitiveEdgeType getEdgeType() const { return fEdgeType; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
@@ -183,7 +182,6 @@
     inline bool isFilled() const { return GrProcessorEdgeTypeIsFill(fEdgeType); }
     inline GrPrimitiveEdgeType getEdgeType() const { return fEdgeType; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
diff --git a/src/gpu/effects/GrBicubicEffect.cpp b/src/gpu/effects/GrBicubicEffect.cpp
index de93aaa..50a2a5d 100644
--- a/src/gpu/effects/GrBicubicEffect.cpp
+++ b/src/gpu/effects/GrBicubicEffect.cpp
@@ -13,32 +13,6 @@
 #include "glsl/GrGLSLUniformHandler.h"
 #include "../private/GrGLSL.h"
 
-/*
- * Filter weights come from Don Mitchell & Arun Netravali's 'Reconstruction Filters in Computer
- * Graphics', ACM SIGGRAPH Computer Graphics 22, 4 (Aug. 1988).
- * ACM DL: http://dl.acm.org/citation.cfm?id=378514
- * Free  : http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf
- *
- * The authors define a family of cubic filters with two free parameters (B and C):
- *
- *            { (12 - 9B - 6C)|x|^3 + (-18 + 12B + 6C)|x|^2 + (6 - 2B)            if |x| < 1
- * k(x) = 1/6 { (-B - 6C)|x|^3 + (6B + 30C)|x|^2 + (-12B - 48C)|x| + (8B + 24C)   if 1 <= |x| < 2
- *            { 0                                                                 otherwise
- *
- * Various well-known cubic splines can be generated, and the authors select (1/3, 1/3) as their
- * favorite overall spline - this is now commonly known as the Mitchell filter, and is the source
- * of the specific weights below.
- *
- * These weights are in column-major order (ie this matrix is transposed from what you'd expect),
- * so we can upload them directly via setMatrix4f.
- */
-static constexpr float kMitchellCoefficients[16] = {
-     1.0f / 18.0f,  16.0f / 18.0f,   1.0f / 18.0f,  0.0f / 18.0f,
-    -9.0f / 18.0f,   0.0f / 18.0f,   9.0f / 18.0f,  0.0f / 18.0f,
-    15.0f / 18.0f, -36.0f / 18.0f,  27.0f / 18.0f, -6.0f / 18.0f,
-    -7.0f / 18.0f,  21.0f / 18.0f, -21.0f / 18.0f,  7.0f / 18.0f,
-};
-
 class GrGLBicubicEffect : public GrGLSLFragmentProcessor {
 public:
     void emitCode(EmitArgs&) override;
@@ -56,7 +30,6 @@
 private:
     typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
 
-    UniformHandle               fCoefficientsUni;
     UniformHandle               fImageIncrementUni;
     UniformHandle               fColorSpaceXformUni;
     GrTextureDomain::GLDomain   fDomain;
@@ -68,48 +41,52 @@
     const GrBicubicEffect& bicubicEffect = args.fFp.cast<GrBicubicEffect>();
 
     GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
-    fCoefficientsUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                  kMat44f_GrSLType, kDefault_GrSLPrecision,
-                                                  "Coefficients");
     fImageIncrementUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
                                                     kVec2f_GrSLType, kDefault_GrSLPrecision,
                                                     "ImageIncrement");
 
     const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni);
-    const char* coeff = uniformHandler->getUniformCStr(fCoefficientsUni);
 
     GrGLSLColorSpaceXformHelper colorSpaceHelper(uniformHandler, bicubicEffect.colorSpaceXform(),
                                                  &fColorSpaceXformUni);
 
-    SkString cubicBlendName;
-
-    static const GrShaderVar gCubicBlendArgs[] = {
-        GrShaderVar("coefficients",  kMat44f_GrSLType),
-        GrShaderVar("t",             kFloat_GrSLType),
-        GrShaderVar("c0",            kVec4f_GrSLType),
-        GrShaderVar("c1",            kVec4f_GrSLType),
-        GrShaderVar("c2",            kVec4f_GrSLType),
-        GrShaderVar("c3",            kVec4f_GrSLType),
-    };
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
     SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
-    fragBuilder->emitFunction(kVec4f_GrSLType,
-                              "cubicBlend",
-                              SK_ARRAY_COUNT(gCubicBlendArgs),
-                              gCubicBlendArgs,
-                              "\tvec4 ts = vec4(1.0, t, t * t, t * t * t);\n"
-                              "\tvec4 c = coefficients * ts;\n"
-                              "\treturn c.x * c0 + c.y * c1 + c.z * c2 + c.w * c3;\n",
-                              &cubicBlendName);
-    fragBuilder->codeAppendf("\tvec2 coord = %s - %s * vec2(0.5);\n", coords2D.c_str(), imgInc);
+
+    /*
+     * Filter weights come from Don Mitchell & Arun Netravali's 'Reconstruction Filters in Computer
+     * Graphics', ACM SIGGRAPH Computer Graphics 22, 4 (Aug. 1988).
+     * ACM DL: http://dl.acm.org/citation.cfm?id=378514
+     * Free  : http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf
+     *
+     * The authors define a family of cubic filters with two free parameters (B and C):
+     *
+     *            { (12 - 9B - 6C)|x|^3 + (-18 + 12B + 6C)|x|^2 + (6 - 2B)          if |x| < 1
+     * k(x) = 1/6 { (-B - 6C)|x|^3 + (6B + 30C)|x|^2 + (-12B - 48C)|x| + (8B + 24C) if 1 <= |x| < 2
+     *            { 0                                                               otherwise
+     *
+     * Various well-known cubic splines can be generated, and the authors select (1/3, 1/3) as their
+     * favorite overall spline - this is now commonly known as the Mitchell filter, and is the
+     * source of the specific weights below.
+     *
+     * This is GLSL, so the matrix is column-major (transposed from standard matrix notation).
+     */
+    fragBuilder->codeAppend("mat4 kMitchellCoefficients = mat4("
+                            " 1.0 / 18.0,  16.0 / 18.0,   1.0 / 18.0,  0.0 / 18.0,"
+                            "-9.0 / 18.0,   0.0 / 18.0,   9.0 / 18.0,  0.0 / 18.0,"
+                            "15.0 / 18.0, -36.0 / 18.0,  27.0 / 18.0, -6.0 / 18.0,"
+                            "-7.0 / 18.0,  21.0 / 18.0, -21.0 / 18.0,  7.0 / 18.0);");
+    fragBuilder->codeAppendf("vec2 coord = %s - %s * vec2(0.5);", coords2D.c_str(), imgInc);
     // We unnormalize the coord in order to determine our fractional offset (f) within the texel
     // We then snap coord to a texel center and renormalize. The snap prevents cases where the
     // starting coords are near a texel boundary and accumulations of imgInc would cause us to skip/
     // double hit a texel.
-    fragBuilder->codeAppendf("\tcoord /= %s;\n", imgInc);
-    fragBuilder->codeAppend("\tvec2 f = fract(coord);\n");
-    fragBuilder->codeAppendf("\tcoord = (coord - f + vec2(0.5)) * %s;\n", imgInc);
-    fragBuilder->codeAppend("\tvec4 rowColors[4];\n");
+    fragBuilder->codeAppendf("coord /= %s;", imgInc);
+    fragBuilder->codeAppend("vec2 f = fract(coord);");
+    fragBuilder->codeAppendf("coord = (coord - f + vec2(0.5)) * %s;", imgInc);
+    fragBuilder->codeAppend("vec4 wx = kMitchellCoefficients * vec4(1.0, f.x, f.x * f.x, f.x * f.x * f.x);");
+    fragBuilder->codeAppend("vec4 wy = kMitchellCoefficients * vec4(1.0, f.y, f.y * f.y, f.y * f.y * f.y);");
+    fragBuilder->codeAppend("vec4 rowColors[4];");
     for (int y = 0; y < 4; ++y) {
         for (int x = 0; x < 4; ++x) {
             SkString coord;
@@ -125,17 +102,16 @@
                                   args.fTexSamplers[0]);
         }
         fragBuilder->codeAppendf(
-            "\tvec4 s%d = %s(%s, f.x, rowColors[0], rowColors[1], rowColors[2], rowColors[3]);\n",
-            y, cubicBlendName.c_str(), coeff);
+            "vec4 s%d = wx.x * rowColors[0] + wx.y * rowColors[1] + wx.z * rowColors[2] + wx.w * rowColors[3];",
+            y);
     }
-    SkString bicubicColor;
-    bicubicColor.printf("%s(%s, f.y, s0, s1, s2, s3)", cubicBlendName.c_str(), coeff);
+    SkString bicubicColor("(wy.x * s0 + wy.y * s1 + wy.z * s2 + wy.w * s3)");
     if (colorSpaceHelper.getXformMatrix()) {
         SkString xformedColor;
         fragBuilder->appendColorGamutXform(&xformedColor, bicubicColor.c_str(), &colorSpaceHelper);
         bicubicColor.swap(xformedColor);
     }
-    fragBuilder->codeAppendf("\t%s = %s;\n",
+    fragBuilder->codeAppendf("%s = %s;",
                              args.fOutputColor, (GrGLSLExpr4(bicubicColor.c_str()) *
                                                  GrGLSLExpr4(args.fInputColor)).c_str());
 }
@@ -148,8 +124,7 @@
     imageIncrement[0] = 1.0f / texture->width();
     imageIncrement[1] = 1.0f / texture->height();
     pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
-    pdman.setMatrix4f(fCoefficientsUni, kMitchellCoefficients);
-    fDomain.setData(pdman, bicubicEffect.domain(), texture->origin());
+    fDomain.setData(pdman, bicubicEffect.domain(), texture);
     if (SkToBool(bicubicEffect.colorSpaceXform())) {
         pdman.setSkMatrix44(fColorSpaceXformUni, bicubicEffect.colorSpaceXform()->srcToDst());
     }
@@ -171,7 +146,7 @@
                                  const SkRect& domain)
   : INHERITED(texture, std::move(colorSpaceXform), matrix,
               GrSamplerParams(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode))
-  , fDomain(domain, GrTextureDomain::kClamp_Mode) {
+  , fDomain(texture, domain, GrTextureDomain::kClamp_Mode) {
     this->initClassID<GrBicubicEffect>();
 }
 
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 8be4c29..3c4566e 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -41,13 +41,11 @@
 
         GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
         // Setup pass through color
-        if (!cte.colorIgnored()) {
-            if (cte.hasVertexColor()) {
-                varyingHandler->addPassThroughAttribute(cte.inColor(), args.fOutputColor);
-            } else {
-                this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor,
-                                        &fColorUniform);
-            }
+        if (cte.hasVertexColor()) {
+            varyingHandler->addPassThroughAttribute(cte.inColor(), args.fOutputColor);
+        } else {
+            this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor,
+                                    &fColorUniform);
         }
 
         // Setup position
@@ -63,15 +61,13 @@
                              args.fFPCoordTransformHandler);
 
         if (cte.maskFormat() == kARGB_GrMaskFormat) {
-            if (!cte.colorIgnored()) {
-                fragBuilder->codeAppendf("%s = ", args.fOutputColor);
-                fragBuilder->appendTextureLookupAndModulate(args.fOutputColor,
-                                                            args.fTexSamplers[0],
-                                                            v.fsIn(),
-                                                            kVec2f_GrSLType);
-                fragBuilder->codeAppend(";");
-                fragBuilder->codeAppendf("%s = vec4(1);", args.fOutputCoverage);
-            }
+            fragBuilder->codeAppendf("%s = ", args.fOutputColor);
+            fragBuilder->appendTextureLookupAndModulate(args.fOutputColor,
+                                                        args.fTexSamplers[0],
+                                                        v.fsIn(),
+                                                        kVec2f_GrSLType);
+            fragBuilder->codeAppend(";");
+            fragBuilder->codeAppendf("%s = vec4(1);", args.fOutputCoverage);
         } else {
             fragBuilder->codeAppendf("%s = ", args.fOutputCoverage);
             fragBuilder->appendTextureLookup(args.fTexSamplers[0], v.fsIn(), kVec2f_GrSLType);
@@ -102,9 +98,8 @@
                               GrProcessorKeyBuilder* b) {
         const GrBitmapTextGeoProc& gp = proc.cast<GrBitmapTextGeoProc>();
         uint32_t key = 0;
-        key |= gp.usesLocalCoords() && gp.localMatrix().hasPerspective() ? 0x1 : 0x0;
-        key |= gp.colorIgnored() ? 0x2 : 0x0;
-        key |= gp.maskFormat() << 3;
+        key |= (gp.usesLocalCoords() && gp.localMatrix().hasPerspective()) ? 0x1 : 0x0;
+        key |= gp.maskFormat() << 1;
         b->add32(key);
 
         // Currently we hardcode numbers to convert atlas coordinates to normalized floating point
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.h b/src/gpu/effects/GrBitmapTextGeoProc.h
index bf7f08f..f15de47 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.h
+++ b/src/gpu/effects/GrBitmapTextGeoProc.h
@@ -37,7 +37,6 @@
     const Attribute* inTextureCoords() const { return fInTextureCoords; }
     GrMaskFormat maskFormat() const { return fMaskFormat; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     bool hasVertexColor() const { return SkToBool(fInColor); }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
diff --git a/src/gpu/effects/GrConfigConversionEffect.cpp b/src/gpu/effects/GrConfigConversionEffect.cpp
index f6f8334..8405fcb 100644
--- a/src/gpu/effects/GrConfigConversionEffect.cpp
+++ b/src/gpu/effects/GrConfigConversionEffect.cpp
@@ -237,19 +237,22 @@
         paint1.addColorFragmentProcessor(std::move(pmToUPM1));
         paint1.setPorterDuffXPFactory(SkBlendMode::kSrc);
 
-        readRTC->fillRectToRect(GrNoClip(), paint1, GrAA::kNo, SkMatrix::I(), kDstRect, kSrcRect);
+        readRTC->fillRectToRect(GrNoClip(), std::move(paint1), GrAA::kNo, SkMatrix::I(), kDstRect,
+                                kSrcRect);
 
         readRTC->asTexture()->readPixels(0, 0, kSize, kSize, kConfig, firstRead);
 
         paint2.addColorFragmentProcessor(std::move(upmToPM));
         paint2.setPorterDuffXPFactory(SkBlendMode::kSrc);
 
-        tempRTC->fillRectToRect(GrNoClip(), paint2, GrAA::kNo, SkMatrix::I(), kDstRect, kSrcRect);
+        tempRTC->fillRectToRect(GrNoClip(), std::move(paint2), GrAA::kNo, SkMatrix::I(), kDstRect,
+                                kSrcRect);
 
         paint3.addColorFragmentProcessor(std::move(pmToUPM2));
         paint3.setPorterDuffXPFactory(SkBlendMode::kSrc);
 
-        readRTC->fillRectToRect(GrNoClip(), paint3, GrAA::kNo, SkMatrix::I(), kDstRect, kSrcRect);
+        readRTC->fillRectToRect(GrNoClip(), std::move(paint3), GrAA::kNo, SkMatrix::I(), kDstRect,
+                                kSrcRect);
 
         readRTC->asTexture()->readPixels(0, 0, kSize, kSize, kConfig, secondRead);
 
diff --git a/src/gpu/effects/GrCoverageSetOpXP.cpp b/src/gpu/effects/GrCoverageSetOpXP.cpp
index 73adc49..05e9fad 100644
--- a/src/gpu/effects/GrCoverageSetOpXP.cpp
+++ b/src/gpu/effects/GrCoverageSetOpXP.cpp
@@ -236,77 +236,89 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 //
-GrCoverageSetOpXPFactory::GrCoverageSetOpXPFactory(SkRegion::Op regionOp, bool invertCoverage)
-    : fRegionOp(regionOp)
-    , fInvertCoverage(invertCoverage) {
-    this->initClassID<GrCoverageSetOpXPFactory>();
-}
+constexpr GrCoverageSetOpXPFactory::GrCoverageSetOpXPFactory(SkRegion::Op regionOp,
+                                                             bool invertCoverage)
+        : fRegionOp(regionOp), fInvertCoverage(invertCoverage) {}
 
-sk_sp<GrXPFactory> GrCoverageSetOpXPFactory::Make(SkRegion::Op regionOp, bool invertCoverage) {
+const GrXPFactory* GrCoverageSetOpXPFactory::Get(SkRegion::Op regionOp, bool invertCoverage) {
+    // If these objects are constructed as static constexpr by cl.exe (2015 SP2) the vtables are
+    // null.
+#ifdef SK_BUILD_FOR_WIN
+#define _CONSTEXPR_
+#else
+#define _CONSTEXPR_ constexpr
+#endif
     switch (regionOp) {
         case SkRegion::kReplace_Op: {
             if (invertCoverage) {
-                static GrCoverageSetOpXPFactory gReplaceCDXPFI(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gReplaceCDXPFI));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gReplaceCDXPFI(
+                        SkRegion::kReplace_Op, true);
+                return &gReplaceCDXPFI;
             } else {
-                static GrCoverageSetOpXPFactory gReplaceCDXPF(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gReplaceCDXPF));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gReplaceCDXPF(
+                        SkRegion::kReplace_Op, false);
+                return &gReplaceCDXPF;
             }
-            break;
         }
         case SkRegion::kIntersect_Op: {
             if (invertCoverage) {
-                static GrCoverageSetOpXPFactory gIntersectCDXPFI(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gIntersectCDXPFI));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gIntersectCDXPFI(
+                        SkRegion::kIntersect_Op, true);
+                return &gIntersectCDXPFI;
             } else {
-                static GrCoverageSetOpXPFactory gIntersectCDXPF(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gIntersectCDXPF));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gIntersectCDXPF(
+                        SkRegion::kIntersect_Op, false);
+                return &gIntersectCDXPF;
             }
-            break;
         }
         case SkRegion::kUnion_Op: {
             if (invertCoverage) {
-                static GrCoverageSetOpXPFactory gUnionCDXPFI(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gUnionCDXPFI));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gUnionCDXPFI(SkRegion::kUnion_Op,
+                                                                               true);
+                return &gUnionCDXPFI;
             } else {
-                static GrCoverageSetOpXPFactory gUnionCDXPF(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gUnionCDXPF));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gUnionCDXPF(SkRegion::kUnion_Op,
+                                                                              false);
+                return &gUnionCDXPF;
             }
-            break;
         }
         case SkRegion::kXOR_Op: {
             if (invertCoverage) {
-                static GrCoverageSetOpXPFactory gXORCDXPFI(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gXORCDXPFI));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gXORCDXPFI(SkRegion::kXOR_Op,
+                                                                             true);
+                return &gXORCDXPFI;
             } else {
-                static GrCoverageSetOpXPFactory gXORCDXPF(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gXORCDXPF));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gXORCDXPF(SkRegion::kXOR_Op,
+                                                                            false);
+                return &gXORCDXPF;
             }
-            break;
         }
         case SkRegion::kDifference_Op: {
             if (invertCoverage) {
-                static GrCoverageSetOpXPFactory gDifferenceCDXPFI(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gDifferenceCDXPFI));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gDifferenceCDXPFI(
+                        SkRegion::kDifference_Op, true);
+                return &gDifferenceCDXPFI;
             } else {
-                static GrCoverageSetOpXPFactory gDifferenceCDXPF(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gDifferenceCDXPF));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gDifferenceCDXPF(
+                        SkRegion::kDifference_Op, false);
+                return &gDifferenceCDXPF;
             }
-            break;
         }
         case SkRegion::kReverseDifference_Op: {
             if (invertCoverage) {
-                static GrCoverageSetOpXPFactory gRevDiffCDXPFI(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gRevDiffCDXPFI));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gRevDiffCDXPFI(
+                        SkRegion::kReverseDifference_Op, true);
+                return &gRevDiffCDXPFI;
             } else {
-                static GrCoverageSetOpXPFactory gRevDiffCDXPF(regionOp, invertCoverage);
-                return sk_sp<GrXPFactory>(SkRef(&gRevDiffCDXPF));
+                static _CONSTEXPR_ const GrCoverageSetOpXPFactory gRevDiffCDXPF(
+                        SkRegion::kReverseDifference_Op, false);
+                return &gRevDiffCDXPF;
             }
-            break;
         }
-        default:
-            return nullptr;
     }
+#undef _CONSTEXPR_
+    SkFAIL("Unknown region op.");
+    return nullptr;
 }
 
 GrXferProcessor* GrCoverageSetOpXPFactory::onCreateXferProcessor(const GrCaps& caps,
@@ -335,8 +347,8 @@
 
 GR_DEFINE_XP_FACTORY_TEST(GrCoverageSetOpXPFactory);
 
-sk_sp<GrXPFactory> GrCoverageSetOpXPFactory::TestCreate(GrProcessorTestData* d) {
+const GrXPFactory* GrCoverageSetOpXPFactory::TestGet(GrProcessorTestData* d) {
     SkRegion::Op regionOp = SkRegion::Op(d->fRandom->nextULessThan(SkRegion::kLastOp + 1));
     bool invertCoverage = !d->fRenderTargetContext->hasMixedSamples() && d->fRandom->nextBool();
-    return GrCoverageSetOpXPFactory::Make(regionOp, invertCoverage);
+    return GrCoverageSetOpXPFactory::Get(regionOp, invertCoverage);
 }
diff --git a/src/gpu/effects/GrCustomXfermode.cpp b/src/gpu/effects/GrCustomXfermode.cpp
index 2ae8f9c..d96ab94 100644
--- a/src/gpu/effects/GrCustomXfermode.cpp
+++ b/src/gpu/effects/GrCustomXfermode.cpp
@@ -31,25 +31,26 @@
 // Static helpers
 ///////////////////////////////////////////////////////////////////////////////
 
-static GrBlendEquation hw_blend_equation(SkBlendMode mode) {
-    enum { kOffset = kOverlay_GrBlendEquation - (int)SkBlendMode::kOverlay };
-    return static_cast<GrBlendEquation>((int)mode + kOffset);
-
-    GR_STATIC_ASSERT(kOverlay_GrBlendEquation == (int)SkBlendMode::kOverlay + kOffset);
-    GR_STATIC_ASSERT(kDarken_GrBlendEquation == (int)SkBlendMode::kDarken + kOffset);
-    GR_STATIC_ASSERT(kLighten_GrBlendEquation == (int)SkBlendMode::kLighten + kOffset);
-    GR_STATIC_ASSERT(kColorDodge_GrBlendEquation == (int)SkBlendMode::kColorDodge + kOffset);
-    GR_STATIC_ASSERT(kColorBurn_GrBlendEquation == (int)SkBlendMode::kColorBurn + kOffset);
-    GR_STATIC_ASSERT(kHardLight_GrBlendEquation == (int)SkBlendMode::kHardLight + kOffset);
-    GR_STATIC_ASSERT(kSoftLight_GrBlendEquation == (int)SkBlendMode::kSoftLight + kOffset);
-    GR_STATIC_ASSERT(kDifference_GrBlendEquation == (int)SkBlendMode::kDifference + kOffset);
-    GR_STATIC_ASSERT(kExclusion_GrBlendEquation == (int)SkBlendMode::kExclusion + kOffset);
-    GR_STATIC_ASSERT(kMultiply_GrBlendEquation == (int)SkBlendMode::kMultiply + kOffset);
-    GR_STATIC_ASSERT(kHSLHue_GrBlendEquation == (int)SkBlendMode::kHue + kOffset);
-    GR_STATIC_ASSERT(kHSLSaturation_GrBlendEquation == (int)SkBlendMode::kSaturation + kOffset);
-    GR_STATIC_ASSERT(kHSLColor_GrBlendEquation == (int)SkBlendMode::kColor + kOffset);
-    GR_STATIC_ASSERT(kHSLLuminosity_GrBlendEquation == (int)SkBlendMode::kLuminosity + kOffset);
-    GR_STATIC_ASSERT(kGrBlendEquationCnt == (int)SkBlendMode::kLastMode + 1 + kOffset);
+static constexpr GrBlendEquation hw_blend_equation(SkBlendMode mode) {
+// In C++14 this could be a constexpr int variable.
+#define EQ_OFFSET (kOverlay_GrBlendEquation - (int)SkBlendMode::kOverlay)
+    GR_STATIC_ASSERT(kOverlay_GrBlendEquation == (int)SkBlendMode::kOverlay + EQ_OFFSET);
+    GR_STATIC_ASSERT(kDarken_GrBlendEquation == (int)SkBlendMode::kDarken + EQ_OFFSET);
+    GR_STATIC_ASSERT(kLighten_GrBlendEquation == (int)SkBlendMode::kLighten + EQ_OFFSET);
+    GR_STATIC_ASSERT(kColorDodge_GrBlendEquation == (int)SkBlendMode::kColorDodge + EQ_OFFSET);
+    GR_STATIC_ASSERT(kColorBurn_GrBlendEquation == (int)SkBlendMode::kColorBurn + EQ_OFFSET);
+    GR_STATIC_ASSERT(kHardLight_GrBlendEquation == (int)SkBlendMode::kHardLight + EQ_OFFSET);
+    GR_STATIC_ASSERT(kSoftLight_GrBlendEquation == (int)SkBlendMode::kSoftLight + EQ_OFFSET);
+    GR_STATIC_ASSERT(kDifference_GrBlendEquation == (int)SkBlendMode::kDifference + EQ_OFFSET);
+    GR_STATIC_ASSERT(kExclusion_GrBlendEquation == (int)SkBlendMode::kExclusion + EQ_OFFSET);
+    GR_STATIC_ASSERT(kMultiply_GrBlendEquation == (int)SkBlendMode::kMultiply + EQ_OFFSET);
+    GR_STATIC_ASSERT(kHSLHue_GrBlendEquation == (int)SkBlendMode::kHue + EQ_OFFSET);
+    GR_STATIC_ASSERT(kHSLSaturation_GrBlendEquation == (int)SkBlendMode::kSaturation + EQ_OFFSET);
+    GR_STATIC_ASSERT(kHSLColor_GrBlendEquation == (int)SkBlendMode::kColor + EQ_OFFSET);
+    GR_STATIC_ASSERT(kHSLLuminosity_GrBlendEquation == (int)SkBlendMode::kLuminosity + EQ_OFFSET);
+    GR_STATIC_ASSERT(kGrBlendEquationCnt == (int)SkBlendMode::kLastMode + 1 + EQ_OFFSET);
+    return static_cast<GrBlendEquation>((int)mode + EQ_OFFSET);
+#undef EQ_OFFSET
 }
 
 static bool can_use_hw_blend_equation(GrBlendEquation equation,
@@ -153,12 +154,8 @@
 
         // Apply coverage by multiplying it into the src color before blending. Mixed samples will
         // "just work" automatically. (See onGetOptimizations())
-        if (args.fInputCoverage) {
-            fragBuilder->codeAppendf("%s = %s * %s;",
-                                     args.fOutputPrimary, args.fInputCoverage, args.fInputColor);
-        } else {
-            fragBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, args.fInputColor);
-        }
+        fragBuilder->codeAppendf("%s = %s * %s;", args.fOutputPrimary, args.fInputCoverage,
+                                 args.fInputColor);
     }
 
     void emitBlendCodeForDstRead(GrGLSLXPFragmentBuilder* fragBuilder,
@@ -306,9 +303,6 @@
     if (analysis.fColorPOI.allStagesMultiplyInput()) {
         flags |= kCanTweakAlphaForCoverage_OptFlag;
     }
-    if (this->hasHWBlendEquation() && analysis.fCoveragePOI.isSolidWhite()) {
-        flags |= kIgnoreCoverage_OptFlag;
-    }
     return flags;
 }
 
@@ -326,9 +320,16 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+
+// See the comment above GrXPFactory's definition about this warning suppression.
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
 class CustomXPFactory : public GrXPFactory {
 public:
-    CustomXPFactory(SkBlendMode mode);
+    constexpr CustomXPFactory(SkBlendMode mode)
+            : fMode(mode), fHWBlendEquation(hw_blend_equation(mode)) {}
 
     void getInvariantBlendedColor(const GrProcOptInfo& colorPOI,
                                   GrXPFactory::InvariantBlendedColor*) const override;
@@ -341,10 +342,6 @@
 
     bool onWillReadDstColor(const GrCaps&, const GrPipelineAnalysis&) const override;
 
-    bool onIsEqual(const GrXPFactory& xpfBase) const override {
-        const CustomXPFactory& xpf = xpfBase.cast<CustomXPFactory>();
-        return fMode == xpf.fMode;
-    }
 
     GR_DECLARE_XP_FACTORY_TEST;
 
@@ -353,18 +350,15 @@
 
     typedef GrXPFactory INHERITED;
 };
-
-CustomXPFactory::CustomXPFactory(SkBlendMode mode)
-    : fMode(mode),
-      fHWBlendEquation(hw_blend_equation(mode)) {
-    SkASSERT(GrCustomXfermode::IsSupportedMode(fMode));
-    this->initClassID<CustomXPFactory>();
-}
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic pop
+#endif
 
 GrXferProcessor* CustomXPFactory::onCreateXferProcessor(const GrCaps& caps,
                                                         const GrPipelineAnalysis& analysis,
                                                         bool hasMixedSamples,
                                                         const DstTexture* dstTexture) const {
+    SkASSERT(GrCustomXfermode::IsSupportedMode(fMode));
     if (can_use_hw_blend_equation(fHWBlendEquation, analysis, caps)) {
         SkASSERT(!dstTexture || !dstTexture->texture());
         return new CustomXP(fMode, fHWBlendEquation);
@@ -384,19 +378,69 @@
 }
 
 GR_DEFINE_XP_FACTORY_TEST(CustomXPFactory);
-sk_sp<GrXPFactory> CustomXPFactory::TestCreate(GrProcessorTestData* d) {
+const GrXPFactory* CustomXPFactory::TestGet(GrProcessorTestData* d) {
     int mode = d->fRandom->nextRangeU((int)SkBlendMode::kLastCoeffMode + 1,
                                       (int)SkBlendMode::kLastSeparableMode);
 
-    return sk_sp<GrXPFactory>(new CustomXPFactory(static_cast<SkBlendMode>(mode)));
+    return GrCustomXfermode::Get((SkBlendMode)mode);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
-sk_sp<GrXPFactory> GrCustomXfermode::MakeXPFactory(SkBlendMode mode) {
-    if (!GrCustomXfermode::IsSupportedMode(mode)) {
-        return nullptr;
-    } else {
-        return sk_sp<GrXPFactory>(new CustomXPFactory(mode));
+const GrXPFactory* GrCustomXfermode::Get(SkBlendMode mode) {
+    // If these objects are constructed as static constexpr by cl.exe (2015 SP2) the vtables are
+    // null.
+#ifdef SK_BUILD_FOR_WIN
+#define _CONSTEXPR_
+#else
+#define _CONSTEXPR_ constexpr
+#endif
+    static _CONSTEXPR_ const CustomXPFactory gOverlay(SkBlendMode::kOverlay);
+    static _CONSTEXPR_ const CustomXPFactory gDarken(SkBlendMode::kDarken);
+    static _CONSTEXPR_ const CustomXPFactory gLighten(SkBlendMode::kLighten);
+    static _CONSTEXPR_ const CustomXPFactory gColorDodge(SkBlendMode::kColorDodge);
+    static _CONSTEXPR_ const CustomXPFactory gColorBurn(SkBlendMode::kColorBurn);
+    static _CONSTEXPR_ const CustomXPFactory gHardLight(SkBlendMode::kHardLight);
+    static _CONSTEXPR_ const CustomXPFactory gSoftLight(SkBlendMode::kSoftLight);
+    static _CONSTEXPR_ const CustomXPFactory gDifference(SkBlendMode::kDifference);
+    static _CONSTEXPR_ const CustomXPFactory gExclusion(SkBlendMode::kExclusion);
+    static _CONSTEXPR_ const CustomXPFactory gMultiply(SkBlendMode::kMultiply);
+    static _CONSTEXPR_ const CustomXPFactory gHue(SkBlendMode::kHue);
+    static _CONSTEXPR_ const CustomXPFactory gSaturation(SkBlendMode::kSaturation);
+    static _CONSTEXPR_ const CustomXPFactory gColor(SkBlendMode::kColor);
+    static _CONSTEXPR_ const CustomXPFactory gLuminosity(SkBlendMode::kLuminosity);
+#undef _CONSTEXPR_
+    switch (mode) {
+        case SkBlendMode::kOverlay:
+            return &gOverlay;
+        case SkBlendMode::kDarken:
+            return &gDarken;
+        case SkBlendMode::kLighten:
+            return &gLighten;
+        case SkBlendMode::kColorDodge:
+            return &gColorDodge;
+        case SkBlendMode::kColorBurn:
+            return &gColorBurn;
+        case SkBlendMode::kHardLight:
+            return &gHardLight;
+        case SkBlendMode::kSoftLight:
+            return &gSoftLight;
+        case SkBlendMode::kDifference:
+            return &gDifference;
+        case SkBlendMode::kExclusion:
+            return &gExclusion;
+        case SkBlendMode::kMultiply:
+            return &gMultiply;
+        case SkBlendMode::kHue:
+            return &gHue;
+        case SkBlendMode::kSaturation:
+            return &gSaturation;
+        case SkBlendMode::kColor:
+            return &gColor;
+        case SkBlendMode::kLuminosity:
+            return &gLuminosity;
+        default:
+            SkASSERT(!GrCustomXfermode::IsSupportedMode(mode));
+            return nullptr;
     }
 }
diff --git a/src/gpu/effects/GrDisableColorXP.cpp b/src/gpu/effects/GrDisableColorXP.cpp
index cfc7e03..69c34ec 100644
--- a/src/gpu/effects/GrDisableColorXP.cpp
+++ b/src/gpu/effects/GrDisableColorXP.cpp
@@ -33,7 +33,7 @@
                                                  bool doesStencilWrite,
                                                  GrColor* color,
                                                  const GrCaps& caps) const override {
-        return GrXferProcessor::kIgnoreColor_OptFlag | GrXferProcessor::kIgnoreCoverage_OptFlag;
+        return GrXferProcessor::kIgnoreColor_OptFlag;
     }
 
     void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override;
@@ -88,11 +88,6 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-
-GrDisableColorXPFactory::GrDisableColorXPFactory() {
-    this->initClassID<GrDisableColorXPFactory>();
-}
-
 GrXferProcessor* GrDisableColorXPFactory::onCreateXferProcessor(const GrCaps& caps,
                                                                 const GrPipelineAnalysis& analysis,
                                                                 bool hasMixedSamples,
@@ -103,6 +98,6 @@
 
 GR_DEFINE_XP_FACTORY_TEST(GrDisableColorXPFactory);
 
-sk_sp<GrXPFactory> GrDisableColorXPFactory::TestCreate(GrProcessorTestData*) {
-    return GrDisableColorXPFactory::Make();
+const GrXPFactory* GrDisableColorXPFactory::TestGet(GrProcessorTestData*) {
+    return GrDisableColorXPFactory::Get();
 }
diff --git a/src/gpu/effects/GrDisableColorXP.h b/src/gpu/effects/GrDisableColorXP.h
index cc02f41..9f56ae5 100644
--- a/src/gpu/effects/GrDisableColorXP.h
+++ b/src/gpu/effects/GrDisableColorXP.h
@@ -14,9 +14,14 @@
 
 class GrProcOptInfo;
 
+// See the comment above GrXPFactory's definition about this warning suppression.
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
 class GrDisableColorXPFactory : public GrXPFactory {
 public:
-    static sk_sp<GrXPFactory> Make() { return sk_sp<GrXPFactory>(new GrDisableColorXPFactory); }
+    static const GrXPFactory* Get();
 
     void getInvariantBlendedColor(const GrProcOptInfo& colorPOI,
                                   GrXPFactory::InvariantBlendedColor* blendedColor) const override {
@@ -25,7 +30,7 @@
     }
 
 private:
-    GrDisableColorXPFactory();
+    constexpr GrDisableColorXPFactory() {}
 
     GrXferProcessor* onCreateXferProcessor(const GrCaps& caps,
                                            const GrPipelineAnalysis&,
@@ -36,13 +41,22 @@
         return false;
     }
 
-    bool onIsEqual(const GrXPFactory& xpfBase) const override {
-        return true;
-    }
-
     GR_DECLARE_XP_FACTORY_TEST;
 
     typedef GrXPFactory INHERITED;
 };
+#if defined(__GNUC__) || defined(__clang)
+#pragma GCC diagnostic pop
+#endif
+
+inline const GrXPFactory* GrDisableColorXPFactory::Get() {
+    // If this is constructed as static constexpr by cl.exe (2015 SP2) the vtable is null.
+#ifdef SK_BUILD_FOR_WIN
+    static const GrDisableColorXPFactory gDisableColorXPFactory;
+#else
+    static constexpr const GrDisableColorXPFactory gDisableColorXPFactory;
+#endif
+    return &gDisableColorXPFactory;
+}
 
 #endif
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 9d90ac9..ee28760 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -53,9 +53,7 @@
 #endif
 
         // Setup pass through color
-        if (!dfTexEffect.colorIgnored()) {
-            varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
-        }
+        varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
 
         // Setup position
         this->setupPosition(vertBuilder,
@@ -201,8 +199,7 @@
                               GrProcessorKeyBuilder* b) {
         const GrDistanceFieldA8TextGeoProc& dfTexEffect = gp.cast<GrDistanceFieldA8TextGeoProc>();
         uint32_t key = dfTexEffect.getFlags();
-        key |= dfTexEffect.colorIgnored() << 16;
-        key |= ComputePosKey(dfTexEffect.viewMatrix()) << 25;
+        key |= ComputePosKey(dfTexEffect.viewMatrix()) << 16;
         b->add32(key);
 
         // Currently we hardcode numbers to convert atlas coordinates to normalized floating point
@@ -323,9 +320,7 @@
         varyingHandler->addVarying("TextureCoords", &v, kHigh_GrSLPrecision);
 
         // setup pass through color
-        if (!dfTexEffect.colorIgnored()) {
-            varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
-        }
+        varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
         vertBuilder->codeAppendf("%s = %s;", v.vsOut(), dfTexEffect.inTextureCoords()->fName);
 
         // Setup position
@@ -457,8 +452,7 @@
         const GrDistanceFieldPathGeoProc& dfTexEffect = gp.cast<GrDistanceFieldPathGeoProc>();
 
         uint32_t key = dfTexEffect.getFlags();
-        key |= dfTexEffect.colorIgnored() << 16;
-        key |= ComputePosKey(dfTexEffect.viewMatrix()) << 25;
+        key |= ComputePosKey(dfTexEffect.viewMatrix()) << 16;
         b->add32(key);
     }
 
@@ -561,9 +555,7 @@
         GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
         // setup pass through color
-        if (!dfTexEffect.colorIgnored()) {
-            varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
-        }
+        varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
 
         // Setup position
         this->setupPosition(vertBuilder,
@@ -752,8 +744,7 @@
         const GrDistanceFieldLCDTextGeoProc& dfTexEffect = gp.cast<GrDistanceFieldLCDTextGeoProc>();
 
         uint32_t key = dfTexEffect.getFlags();
-        key |= dfTexEffect.colorIgnored() << 16;
-        key |= ComputePosKey(dfTexEffect.viewMatrix()) << 25;
+        key |= ComputePosKey(dfTexEffect.viewMatrix()) << 16;
         b->add32(key);
 
         // Currently we hardcode numbers to convert atlas coordinates to normalized floating point
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.h b/src/gpu/effects/GrDistanceFieldGeoProc.h
index a0bfdd9..4355e8d 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.h
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.h
@@ -74,7 +74,6 @@
     const Attribute* inColor() const { return fInColor; }
     const Attribute* inTextureCoords() const { return fInTextureCoords; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
 #ifdef SK_GAMMA_APPLY_TO_A8
@@ -135,7 +134,6 @@
     const Attribute* inColor() const { return fInColor; }
     const Attribute* inTextureCoords() const { return fInTextureCoords; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     uint32_t getFlags() const { return fFlags; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
@@ -204,7 +202,6 @@
     const Attribute* inTextureCoords() const { return fInTextureCoords; }
     DistanceAdjust getDistanceAdjust() const { return fDistanceAdjust; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     uint32_t getFlags() const { return fFlags; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
index 96f7014..8b98d0b 100644
--- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp
+++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
@@ -144,7 +144,7 @@
     pdman.set4fv(fKernelUni, arrayCount, conv.kernel());
     pdman.set1f(fGainUni, conv.gain());
     pdman.set1f(fBiasUni, conv.bias());
-    fDomain.setData(pdman, conv.domain(), texture->origin());
+    fDomain.setData(pdman, conv.domain(), texture);
 }
 
 GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture,
@@ -161,7 +161,7 @@
     fGain(SkScalarToFloat(gain)),
     fBias(SkScalarToFloat(bias) / 255.0f),
     fConvolveAlpha(convolveAlpha),
-    fDomain(GrTextureDomain::MakeTexelDomainForMode(texture, bounds, tileMode), tileMode) {
+    fDomain(texture, GrTextureDomain::MakeTexelDomainForMode(bounds, tileMode), tileMode) {
     this->initClassID<GrMatrixConvolutionEffect>();
     for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) {
         fKernel[i] = SkScalarToFloat(kernel[i]);
diff --git a/src/gpu/effects/GrPorterDuffXferProcessor.cpp b/src/gpu/effects/GrPorterDuffXferProcessor.cpp
index a6b5bbb..a813a53 100644
--- a/src/gpu/effects/GrPorterDuffXferProcessor.cpp
+++ b/src/gpu/effects/GrPorterDuffXferProcessor.cpp
@@ -386,45 +386,27 @@
                                 GrGLSLXPFragmentBuilder* fragBuilder,
                                 BlendFormula::OutputType outputType, const char* output,
                                 const char* inColor, const char* inCoverage) {
+    SkASSERT(inCoverage);
+    SkASSERT(inColor);
     switch (outputType) {
         case BlendFormula::kNone_OutputType:
             fragBuilder->codeAppendf("%s = vec4(0.0);", output);
             break;
         case BlendFormula::kCoverage_OutputType:
             // We can have a coverage formula while not reading coverage if there are mixed samples.
-            if (inCoverage) {
-                fragBuilder->codeAppendf("%s = %s;", output, inCoverage);
-            } else {
-                fragBuilder->codeAppendf("%s = vec4(1.0);", output);
-            }
+            fragBuilder->codeAppendf("%s = %s;", output, inCoverage);
             break;
         case BlendFormula::kModulate_OutputType:
-            if (inCoverage) {
-                fragBuilder->codeAppendf("%s = %s * %s;", output, inColor, inCoverage);
-            } else {
-                fragBuilder->codeAppendf("%s = %s;", output, inColor);
-            }
+            fragBuilder->codeAppendf("%s = %s * %s;", output, inColor, inCoverage);
             break;
         case BlendFormula::kSAModulate_OutputType:
-            if (inCoverage) {
-                fragBuilder->codeAppendf("%s = %s.a * %s;", output, inColor, inCoverage);
-            } else {
-                fragBuilder->codeAppendf("%s = %s;", output, inColor);
-            }
+            fragBuilder->codeAppendf("%s = %s.a * %s;", output, inColor, inCoverage);
             break;
         case BlendFormula::kISAModulate_OutputType:
-            if (inCoverage) {
-                fragBuilder->codeAppendf("%s = (1.0 - %s.a) * %s;", output, inColor, inCoverage);
-            } else {
-                fragBuilder->codeAppendf("%s = vec4(1.0 - %s.a);", output, inColor);
-            }
+            fragBuilder->codeAppendf("%s = (1.0 - %s.a) * %s;", output, inColor, inCoverage);
             break;
         case BlendFormula::kISCModulate_OutputType:
-            if (inCoverage) {
-                fragBuilder->codeAppendf("%s = (vec4(1.0) - %s) * %s;", output, inColor, inCoverage);
-            } else {
-                fragBuilder->codeAppendf("%s = vec4(1.0) - %s;", output, inColor);
-            }
+            fragBuilder->codeAppendf("%s = (vec4(1.0) - %s) * %s;", output, inColor, inCoverage);
             break;
         default:
             SkFAIL("Unsupported output type.");
@@ -482,15 +464,11 @@
             optFlags |= GrXferProcessor::kSkipDraw_OptFlag;
         }
         optFlags |= (GrXferProcessor::kIgnoreColor_OptFlag |
-                     GrXferProcessor::kIgnoreCoverage_OptFlag |
                      GrXferProcessor::kCanTweakAlphaForCoverage_OptFlag);
     } else {
         if (!fBlendFormula.usesInputColor()) {
             optFlags |= GrXferProcessor::kIgnoreColor_OptFlag;
         }
-        if (analysis.fCoveragePOI.isSolidWhite()) {
-            optFlags |= GrXferProcessor::kIgnoreCoverage_OptFlag;
-        }
         if (analysis.fColorPOI.allStagesMultiplyInput() &&
             fBlendFormula.canTweakAlphaForCoverage() &&
             !analysis.fCoveragePOI.isFourChannelOutput()) {
@@ -697,40 +675,71 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrPorterDuffXPFactory::GrPorterDuffXPFactory(SkBlendMode xfermode)
-    : fXfermode(xfermode) {
-    SkASSERT((unsigned)fXfermode <= (unsigned)SkBlendMode::kLastCoeffMode);
-    this->initClassID<GrPorterDuffXPFactory>();
-}
+constexpr GrPorterDuffXPFactory::GrPorterDuffXPFactory(SkBlendMode xfermode)
+        : fBlendMode(xfermode) {}
 
-sk_sp<GrXPFactory> GrPorterDuffXPFactory::Make(SkBlendMode xfermode) {
-    static GrPorterDuffXPFactory gClearPDXPF(SkBlendMode::kClear);
-    static GrPorterDuffXPFactory gSrcPDXPF(SkBlendMode::kSrc);
-    static GrPorterDuffXPFactory gDstPDXPF(SkBlendMode::kDst);
-    static GrPorterDuffXPFactory gSrcOverPDXPF(SkBlendMode::kSrcOver);
-    static GrPorterDuffXPFactory gDstOverPDXPF(SkBlendMode::kDstOver);
-    static GrPorterDuffXPFactory gSrcInPDXPF(SkBlendMode::kSrcIn);
-    static GrPorterDuffXPFactory gDstInPDXPF(SkBlendMode::kDstIn);
-    static GrPorterDuffXPFactory gSrcOutPDXPF(SkBlendMode::kSrcOut);
-    static GrPorterDuffXPFactory gDstOutPDXPF(SkBlendMode::kDstOut);
-    static GrPorterDuffXPFactory gSrcATopPDXPF(SkBlendMode::kSrcATop);
-    static GrPorterDuffXPFactory gDstATopPDXPF(SkBlendMode::kDstATop);
-    static GrPorterDuffXPFactory gXorPDXPF(SkBlendMode::kXor);
-    static GrPorterDuffXPFactory gPlusPDXPF(SkBlendMode::kPlus);
-    static GrPorterDuffXPFactory gModulatePDXPF(SkBlendMode::kModulate);
-    static GrPorterDuffXPFactory gScreenPDXPF(SkBlendMode::kScreen);
+const GrXPFactory* GrPorterDuffXPFactory::Get(SkBlendMode blendMode) {
+    SkASSERT((unsigned)blendMode <= (unsigned)SkBlendMode::kLastCoeffMode);
 
-    static GrPorterDuffXPFactory* gFactories[] = {
-        &gClearPDXPF, &gSrcPDXPF, &gDstPDXPF, &gSrcOverPDXPF, &gDstOverPDXPF, &gSrcInPDXPF,
-        &gDstInPDXPF, &gSrcOutPDXPF, &gDstOutPDXPF, &gSrcATopPDXPF, &gDstATopPDXPF, &gXorPDXPF,
-        &gPlusPDXPF, &gModulatePDXPF, &gScreenPDXPF
-    };
-    GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFactories) == (int)SkBlendMode::kLastCoeffMode + 1);
+    // If these objects are constructed as static constexpr by cl.exe (2015 SP2) the vtables are
+    // null.
+#ifdef SK_BUILD_FOR_WIN
+#define _CONSTEXPR_
+#else
+#define _CONSTEXPR_ constexpr
+#endif
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gClearPDXPF(SkBlendMode::kClear);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcPDXPF(SkBlendMode::kSrc);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gDstPDXPF(SkBlendMode::kDst);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcOverPDXPF(SkBlendMode::kSrcOver);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gDstOverPDXPF(SkBlendMode::kDstOver);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcInPDXPF(SkBlendMode::kSrcIn);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gDstInPDXPF(SkBlendMode::kDstIn);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcOutPDXPF(SkBlendMode::kSrcOut);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gDstOutPDXPF(SkBlendMode::kDstOut);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gSrcATopPDXPF(SkBlendMode::kSrcATop);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gDstATopPDXPF(SkBlendMode::kDstATop);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gXorPDXPF(SkBlendMode::kXor);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gPlusPDXPF(SkBlendMode::kPlus);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gModulatePDXPF(SkBlendMode::kModulate);
+    static _CONSTEXPR_ const GrPorterDuffXPFactory gScreenPDXPF(SkBlendMode::kScreen);
+#undef _CONSTEXPR_
 
-    if ((int)xfermode < 0 || (int)xfermode > (int)SkBlendMode::kLastCoeffMode) {
-        return nullptr;
+    switch (blendMode) {
+        case SkBlendMode::kClear:
+            return &gClearPDXPF;
+        case SkBlendMode::kSrc:
+            return &gSrcPDXPF;
+        case SkBlendMode::kDst:
+            return &gDstPDXPF;
+        case SkBlendMode::kSrcOver:
+            return &gSrcOverPDXPF;
+        case SkBlendMode::kDstOver:
+            return &gDstOverPDXPF;
+        case SkBlendMode::kSrcIn:
+            return &gSrcInPDXPF;
+        case SkBlendMode::kDstIn:
+            return &gDstInPDXPF;
+        case SkBlendMode::kSrcOut:
+            return &gSrcOutPDXPF;
+        case SkBlendMode::kDstOut:
+            return &gDstOutPDXPF;
+        case SkBlendMode::kSrcATop:
+            return &gSrcATopPDXPF;
+        case SkBlendMode::kDstATop:
+            return &gDstATopPDXPF;
+        case SkBlendMode::kXor:
+            return &gXorPDXPF;
+        case SkBlendMode::kPlus:
+            return &gPlusPDXPF;
+        case SkBlendMode::kModulate:
+            return &gModulatePDXPF;
+        case SkBlendMode::kScreen:
+            return &gScreenPDXPF;
+        default:
+            SkFAIL("Unexpected blend mode.");
+            return nullptr;
     }
-    return sk_sp<GrXPFactory>(SkRef(gFactories[(int)xfermode]));
 }
 
 GrXferProcessor* GrPorterDuffXPFactory::onCreateXferProcessor(const GrCaps& caps,
@@ -738,27 +747,27 @@
                                                               bool hasMixedSamples,
                                                               const DstTexture* dstTexture) const {
     if (analysis.fUsesPLSDstRead) {
-        return new ShaderPDXferProcessor(dstTexture, hasMixedSamples, fXfermode);
+        return new ShaderPDXferProcessor(dstTexture, hasMixedSamples, fBlendMode);
     }
     BlendFormula blendFormula;
     if (analysis.fCoveragePOI.isFourChannelOutput()) {
-        if (SkBlendMode::kSrcOver == fXfermode &&
+        if (SkBlendMode::kSrcOver == fBlendMode &&
             kRGBA_GrColorComponentFlags == analysis.fColorPOI.validFlags() &&
             !caps.shaderCaps()->dualSourceBlendingSupport() &&
             !caps.shaderCaps()->dstReadInShaderSupport()) {
             // If we don't have dual source blending or in shader dst reads, we fall back to this
             // trick for rendering SrcOver LCD text instead of doing a dst copy.
             SkASSERT(!dstTexture || !dstTexture->texture());
-            return PDLCDXferProcessor::Create(fXfermode, analysis.fColorPOI);
+            return PDLCDXferProcessor::Create(fBlendMode, analysis.fColorPOI);
         }
-        blendFormula = get_lcd_blend_formula(analysis.fCoveragePOI, fXfermode);
+        blendFormula = get_lcd_blend_formula(analysis.fCoveragePOI, fBlendMode);
     } else {
         blendFormula = get_blend_formula(analysis.fColorPOI, analysis.fCoveragePOI, hasMixedSamples,
-                                         fXfermode);
+                                         fBlendMode);
     }
 
     if (blendFormula.hasSecondaryOutput() && !caps.shaderCaps()->dualSourceBlendingSupport()) {
-        return new ShaderPDXferProcessor(dstTexture, hasMixedSamples, fXfermode);
+        return new ShaderPDXferProcessor(dstTexture, hasMixedSamples, fBlendMode);
     }
 
     SkASSERT(!dstTexture || !dstTexture->texture());
@@ -768,7 +777,7 @@
 void GrPorterDuffXPFactory::getInvariantBlendedColor(const GrProcOptInfo& colorPOI,
                                                      InvariantBlendedColor* blendedColor) const {
     // Find the blended color info based on the formula that does not have coverage.
-    BlendFormula colorFormula = gBlendTable[colorPOI.isOpaque()][0][(int)fXfermode];
+    BlendFormula colorFormula = gBlendTable[colorPOI.isOpaque()][0][(int)fBlendMode];
     if (colorFormula.usesDstColor()) {
         blendedColor->fWillBlendWithDst = true;
         blendedColor->fKnownColorFlags = kNone_GrColorComponentFlags;
@@ -806,12 +815,12 @@
     // blend. The one exception is when we are using srcover mode and we know the input color into
     // the XP.
     if (analysis.fCoveragePOI.isFourChannelOutput()) {
-        if (SkBlendMode::kSrcOver == fXfermode &&
+        if (SkBlendMode::kSrcOver == fBlendMode &&
             kRGBA_GrColorComponentFlags == analysis.fColorPOI.validFlags() &&
             !caps.shaderCaps()->dstReadInShaderSupport()) {
             return false;
         }
-        return get_lcd_blend_formula(analysis.fCoveragePOI, fXfermode).hasSecondaryOutput();
+        return get_lcd_blend_formula(analysis.fCoveragePOI, fBlendMode).hasSecondaryOutput();
     }
 
     // We fallback on the shader XP when the blend formula would use dual source blending but we
@@ -819,15 +828,15 @@
     static const bool kHasMixedSamples = false;
     SkASSERT(!caps.usesMixedSamples()); // We never use mixed samples without dual source blending.
     auto formula = get_blend_formula(analysis.fColorPOI, analysis.fCoveragePOI, kHasMixedSamples,
-                                     fXfermode);
+                                     fBlendMode);
     return formula.hasSecondaryOutput();
 }
 
 GR_DEFINE_XP_FACTORY_TEST(GrPorterDuffXPFactory);
 
-sk_sp<GrXPFactory> GrPorterDuffXPFactory::TestCreate(GrProcessorTestData* d) {
+const GrXPFactory* GrPorterDuffXPFactory::TestGet(GrProcessorTestData* d) {
     SkBlendMode mode = SkBlendMode(d->fRandom->nextULessThan((int)SkBlendMode::kLastCoeffMode));
-    return GrPorterDuffXPFactory::Make(mode);
+    return GrPorterDuffXPFactory::Get(mode);
 }
 
 void GrPorterDuffXPFactory::TestGetXPOutputTypes(const GrXferProcessor* xp,
diff --git a/src/gpu/effects/GrShadowGeoProc.h b/src/gpu/effects/GrShadowGeoProc.h
index dc6872e..b0631ef 100755
--- a/src/gpu/effects/GrShadowGeoProc.h
+++ b/src/gpu/effects/GrShadowGeoProc.h
@@ -31,7 +31,6 @@
     const Attribute* inColor() const { return fInColor; }
     const Attribute* inShadowParams() const { return fInShadowParams; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
 
     void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override;
diff --git a/src/gpu/effects/GrTextureDomain.cpp b/src/gpu/effects/GrTextureDomain.cpp
index c6ff9f7..d6872e1 100644
--- a/src/gpu/effects/GrTextureDomain.cpp
+++ b/src/gpu/effects/GrTextureDomain.cpp
@@ -17,29 +17,41 @@
 #include "glsl/GrGLSLShaderBuilder.h"
 #include "glsl/GrGLSLUniformHandler.h"
 
-GrTextureDomain::GrTextureDomain(const SkRect& domain, Mode mode, int index)
-    : fIndex(index) {
+static bool can_ignore_rect(GrTexture* tex, const SkRect& domain) {
+    // This logic is relying on the instantiated size of 'tex'. In the deferred world it
+    // will have to change so this logic only fires for kExact texture proxies. This shouldn't
+    // change the actual behavior of Ganesh since shaders shouldn't be accessing pixels outside
+    // of the content rectangle.
+    const SkIRect kFullRect = SkIRect::MakeWH(tex->width(), tex->height());
 
-    static const SkRect kFullRect = {0, 0, SK_Scalar1, SK_Scalar1};
-    if (domain.contains(kFullRect) && kClamp_Mode == mode) {
+    return domain.contains(kFullRect);
+}
+
+GrTextureDomain::GrTextureDomain(GrTexture* tex, const SkRect& domain, Mode mode, int index)
+    : fMode(mode), fIndex(index) {
+
+    if (kIgnore_Mode == fMode) {
+        return;
+    }
+
+    if (kClamp_Mode == mode && can_ignore_rect(tex, domain)) {
         fMode = kIgnore_Mode;
-    } else {
-        fMode = mode;
+        return;
     }
 
-    if (fMode != kIgnore_Mode) {
-        // We don't currently handle domains that are empty or don't intersect the texture.
-        // It is OK if the domain rect is a line or point, but it should not be inverted. We do not
-        // handle rects that do not intersect the [0..1]x[0..1] rect.
-        SkASSERT(domain.fLeft <= domain.fRight);
-        SkASSERT(domain.fTop <= domain.fBottom);
-        fDomain.fLeft = SkScalarPin(domain.fLeft, kFullRect.fLeft, kFullRect.fRight);
-        fDomain.fRight = SkScalarPin(domain.fRight, kFullRect.fLeft, kFullRect.fRight);
-        fDomain.fTop = SkScalarPin(domain.fTop, kFullRect.fTop, kFullRect.fBottom);
-        fDomain.fBottom = SkScalarPin(domain.fBottom, kFullRect.fTop, kFullRect.fBottom);
-        SkASSERT(fDomain.fLeft <= fDomain.fRight);
-        SkASSERT(fDomain.fTop <= fDomain.fBottom);
-    }
+    const SkRect kFullRect = SkRect::MakeIWH(tex->width(), tex->height());
+
+    // We don't currently handle domains that are empty or don't intersect the texture.
+    // It is OK if the domain rect is a line or point, but it should not be inverted. We do not
+    // handle rects that do not intersect the [0..1]x[0..1] rect.
+    SkASSERT(domain.fLeft <= domain.fRight);
+    SkASSERT(domain.fTop <= domain.fBottom);
+    fDomain.fLeft = SkScalarPin(domain.fLeft, 0.0f, kFullRect.fRight);
+    fDomain.fRight = SkScalarPin(domain.fRight, fDomain.fLeft, kFullRect.fRight);
+    fDomain.fTop = SkScalarPin(domain.fTop, 0.0f, kFullRect.fBottom);
+    fDomain.fBottom = SkScalarPin(domain.fBottom, fDomain.fTop, kFullRect.fBottom);
+    SkASSERT(fDomain.fLeft <= fDomain.fRight);
+    SkASSERT(fDomain.fTop <= fDomain.fBottom);
 }
 
 //////////////////////////////////////////////////////////////////////////////
@@ -145,17 +157,26 @@
 
 void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
                                         const GrTextureDomain& textureDomain,
-                                        GrSurfaceOrigin textureOrigin) {
+                                        GrTexture* tex) {
     SkASSERT(textureDomain.mode() == fMode);
     if (kIgnore_Mode != textureDomain.mode()) {
+        SkScalar wInv = SK_Scalar1 / tex->width();
+        SkScalar hInv = SK_Scalar1 / tex->height();
+
         float values[kPrevDomainCount] = {
-            SkScalarToFloat(textureDomain.domain().left()),
-            SkScalarToFloat(textureDomain.domain().top()),
-            SkScalarToFloat(textureDomain.domain().right()),
-            SkScalarToFloat(textureDomain.domain().bottom())
+            SkScalarToFloat(textureDomain.domain().fLeft * wInv),
+            SkScalarToFloat(textureDomain.domain().fTop * hInv),
+            SkScalarToFloat(textureDomain.domain().fRight * wInv),
+            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);
+
         // vertical flip if necessary
-        if (kBottomLeft_GrSurfaceOrigin == textureOrigin) {
+        if (kBottomLeft_GrSurfaceOrigin == tex->origin()) {
             values[1] = 1.0f - values[1];
             values[3] = 1.0f - values[3];
             // The top and bottom were just flipped, so correct the ordering
@@ -177,9 +198,8 @@
                                                        const SkRect& domain,
                                                        GrTextureDomain::Mode mode,
                                                        GrSamplerParams::FilterMode filterMode) {
-    static const SkRect kFullRect = {0, 0, SK_Scalar1, SK_Scalar1};
     if (GrTextureDomain::kIgnore_Mode == mode ||
-        (GrTextureDomain::kClamp_Mode == mode && domain.contains(kFullRect))) {
+        (GrTextureDomain::kClamp_Mode == mode && can_ignore_rect(texture, domain))) {
         return GrSimpleTextureEffect::Make(texture, std::move(colorSpaceXform), matrix, filterMode);
     } else {
         return sk_sp<GrFragmentProcessor>(
@@ -195,7 +215,7 @@
                                              GrTextureDomain::Mode mode,
                                              GrSamplerParams::FilterMode filterMode)
     : GrSingleTextureEffect(texture, std::move(colorSpaceXform), matrix, filterMode)
-    , fTextureDomain(domain, mode) {
+    , fTextureDomain(texture, domain, mode) {
     SkASSERT(mode != GrTextureDomain::kRepeat_Mode ||
             filterMode == GrSamplerParams::kNone_FilterMode);
     this->initClassID<GrTextureDomainEffect>();
@@ -235,7 +255,7 @@
         void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& fp) override {
             const GrTextureDomainEffect& tde = fp.cast<GrTextureDomainEffect>();
             const GrTextureDomain& domain = tde.fTextureDomain;
-            fGLDomain.setData(pdman, domain, tde.textureSampler(0).texture()->origin());
+            fGLDomain.setData(pdman, domain, tde.textureSampler(0).texture());
             if (SkToBool(tde.colorSpaceXform())) {
                 pdman.setSkMatrix44(fColorSpaceXformUni, tde.colorSpaceXform()->srcToDst());
             }
@@ -273,18 +293,19 @@
 sk_sp<GrFragmentProcessor> GrTextureDomainEffect::TestCreate(GrProcessorTestData* d) {
     int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx :
                                           GrProcessorUnitTest::kAlphaTextureIdx;
+    GrTexture* tex = d->fTextures[texIdx];
     SkRect domain;
-    domain.fLeft = d->fRandom->nextUScalar1();
-    domain.fRight = d->fRandom->nextRangeScalar(domain.fLeft, SK_Scalar1);
-    domain.fTop = d->fRandom->nextUScalar1();
-    domain.fBottom = d->fRandom->nextRangeScalar(domain.fTop, SK_Scalar1);
+    domain.fLeft = d->fRandom->nextRangeScalar(0, tex->width());
+    domain.fRight = d->fRandom->nextRangeScalar(domain.fLeft, tex->width());
+    domain.fTop = d->fRandom->nextRangeScalar(0, tex->height());
+    domain.fBottom = d->fRandom->nextRangeScalar(domain.fTop, tex->height());
     GrTextureDomain::Mode mode =
         (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
     const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
     bool bilerp = mode != GrTextureDomain::kRepeat_Mode ? d->fRandom->nextBool() : false;
     auto colorSpaceXform = GrTest::TestColorXform(d->fRandom);
     return GrTextureDomainEffect::Make(
-        d->fTextures[texIdx],
+        tex,
         colorSpaceXform,
         matrix,
         domain,
@@ -303,7 +324,7 @@
 GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor(
         GrTexture* texture, const SkIRect& subset, const SkIPoint& deviceSpaceOffset)
         : fTextureSampler(texture, GrSamplerParams::ClampNoFilter())
-        , fTextureDomain(GrTextureDomain::MakeTexelDomain(texture, subset),
+        , fTextureDomain(texture, GrTextureDomain::MakeTexelDomain(subset),
                          GrTextureDomain::kDecal_Mode) {
     this->addTextureSampler(&fTextureSampler);
     fDeviceSpaceOffset.fX = deviceSpaceOffset.fX - subset.fLeft;
@@ -342,7 +363,7 @@
             const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp =
                     fp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>();
             GrTexture* texture = dstdfp.textureSampler(0).texture();
-            fGLDomain.setData(pdman, dstdfp.fTextureDomain, texture->origin());
+            fGLDomain.setData(pdman, dstdfp.fTextureDomain, texture);
             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 72204b1..66bd220 100644
--- a/src/gpu/effects/GrTextureDomain.h
+++ b/src/gpu/effects/GrTextureDomain.h
@@ -44,8 +44,7 @@
     static const int kModeCount = kLastMode + 1;
 
     static const GrTextureDomain& IgnoredDomain() {
-        static const SkRect gDummyRect = {0, 0, 0, 0};
-        static const GrTextureDomain gDomain(gDummyRect, kIgnore_Mode);
+        static const GrTextureDomain gDomain(nullptr, SkRect::MakeEmpty(), kIgnore_Mode);
         return gDomain;
     }
 
@@ -53,36 +52,22 @@
      * @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(const SkRect& domain, Mode, int index = -1);
+    GrTextureDomain(GrTexture*, const SkRect& domain, Mode, int index = -1);
 
     const SkRect& domain() const { return fDomain; }
     Mode mode() const { return fMode; }
 
     /* 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 GrTexture* texture, const SkIRect& texelRect) {
-        SkScalar wInv = SK_Scalar1 / texture->width();
-        SkScalar hInv = SK_Scalar1 / texture->height();
-        SkRect result = {
-            texelRect.fLeft * wInv,
-            texelRect.fTop * hInv,
-            texelRect.fRight * wInv,
-            texelRect.fBottom * hInv
-        };
-        return result;
+    static const SkRect MakeTexelDomain(const SkIRect& texelRect) {
+        return SkRect::Make(texelRect);
     }
 
-    static const SkRect MakeTexelDomainForMode(const GrTexture* texture, const SkIRect& texelRect, Mode mode) {
+    static const SkRect MakeTexelDomainForMode(const SkIRect& texelRect, Mode mode) {
         // For Clamp mode, inset by half a texel.
-        SkScalar wInv = SK_Scalar1 / texture->width();
-        SkScalar hInv = SK_Scalar1 / texture->height();
         SkScalar inset = (mode == kClamp_Mode && !texelRect.isEmpty()) ? SK_ScalarHalf : 0;
-        return SkRect::MakeLTRB(
-            (texelRect.fLeft + inset) * wInv,
-            (texelRect.fTop + inset) * hInv,
-            (texelRect.fRight - inset) * wInv,
-            (texelRect.fBottom - inset) * hInv
-        );
+        return SkRect::MakeLTRB(texelRect.fLeft + inset, texelRect.fTop + inset,
+                                texelRect.fRight - inset, texelRect.fBottom - inset);
     }
 
     bool operator==(const GrTextureDomain& that) const {
@@ -130,7 +115,7 @@
          * origin.
          */
         void setData(const GrGLSLProgramDataManager& pdman, const GrTextureDomain& textureDomain,
-                     GrSurfaceOrigin textureOrigin);
+                     GrTexture* texure);
 
         enum {
             kDomainKeyBits = 2, // See DomainKey().
@@ -157,8 +142,6 @@
     Mode    fMode;
     SkRect  fDomain;
     int     fIndex;
-
-    typedef GrSingleTextureEffect INHERITED;
 };
 
 /**
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index d3adc28..7a11cf8 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -1726,6 +1726,9 @@
     }
     fConfigTable[kRGBA_4444_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
 
+    fConfigTable[kAlpha_8_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
+    fConfigTable[kAlpha_8_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
+    fConfigTable[kAlpha_8_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
     if (this->textureRedSupport()) {
         fConfigTable[kAlpha_8_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_RED;
         fConfigTable[kAlpha_8_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_R8;
@@ -1742,9 +1745,6 @@
             GR_GL_ALPHA;
         fConfigTable[kAlpha_8_GrPixelConfig].fSwizzle = GrSwizzle::AAAA();
     }
-    fConfigTable[kAlpha_8_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
-    fConfigTable[kAlpha_8_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
-    fConfigTable[kAlpha_8_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
     if (this->textureRedSupport() ||
         (kStandard_MSFBOType == this->msFBOType() && ctxInfo.renderer() != kOSMesa_GrGLRenderer)) {
         // OpenGL 3.0+ (and GL_ARB_framebuffer_object) supports ALPHA8 as renderable.
@@ -1756,6 +1756,39 @@
         fConfigTable[kAlpha_8_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
     }
 
+    fConfigTable[kGray_8_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
+    fConfigTable[kGray_8_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
+    fConfigTable[kGray_8_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
+    if (this->textureRedSupport()) {
+        fConfigTable[kGray_8_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_RED;
+        fConfigTable[kGray_8_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_R8;
+        fConfigTable[kGray_8_GrPixelConfig].fFormats.fExternalFormat[kOther_ExternalFormatUsage] =
+            GR_GL_RED;
+        fConfigTable[kGray_8_GrPixelConfig].fSwizzle = GrSwizzle::RRRA();
+        if (texelBufferSupport) {
+            fConfigTable[kGray_8_GrPixelConfig].fFlags |= ConfigInfo::kCanUseWithTexelBuffer_Flag;
+        }
+    } else {
+        fConfigTable[kGray_8_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_LUMINANCE;
+        fConfigTable[kGray_8_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_LUMINANCE8;
+        fConfigTable[kGray_8_GrPixelConfig].fFormats.fExternalFormat[kOther_ExternalFormatUsage] =
+            GR_GL_LUMINANCE;
+        fConfigTable[kGray_8_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
+    }
+#if 0 // Leaving Gray8 as non-renderable, to keep things simple and match raster
+    if (this->textureRedSupport() ||
+        (kDesktop_ARB_MSFBOType == this->msFBOType() &&
+         ctxInfo.renderer() != kOSMesa_GrGLRenderer)) {
+        // desktop ARB extension/3.0+ supports LUMINANCE8 as renderable.
+        // However, osmesa fails if it used even when GL_ARB_framebuffer_object is present.
+        // Core profile removes LUMINANCE8 support, but we should have chosen R8 in that case.
+        fConfigTable[kGray_8_GrPixelConfig].fFlags |= allRenderFlags;
+    }
+#endif
+    if (texStorageSupported) {
+        fConfigTable[kGray_8_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
+    }
+
     // Check for [half] floating point texture support
     // NOTE: We disallow floating point textures on ES devices if linear filtering modes are not
     // supported. This is for simplicity, but a more granular approach is possible. Coincidentally,
@@ -1809,6 +1842,16 @@
     }
     fConfigTable[kRGBA_float_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
 
+    if (hasHalfFPTextures) {
+        fConfigTable[kAlpha_half_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
+        // ES requires either 3.2 or the combination of EXT_color_buffer_half_float and support for
+        // GL_RED internal format.
+        if (kGL_GrGLStandard == standard || version >= GR_GL_VER(3, 2) ||
+            (this->textureRedSupport() &&
+             ctxInfo.hasExtension("GL_EXT_color_buffer_half_float"))) {
+            fConfigTable[kAlpha_half_GrPixelConfig].fFlags |= fpRenderFlags;
+        }
+    }
     if (this->textureRedSupport()) {
         fConfigTable[kAlpha_half_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_RED;
         fConfigTable[kAlpha_half_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_R16F;
@@ -1836,16 +1879,6 @@
         fConfigTable[kAlpha_half_GrPixelConfig].fFormats.fExternalType = GR_GL_HALF_FLOAT_OES;
     }
     fConfigTable[kAlpha_half_GrPixelConfig].fFormatType = kFloat_FormatType;
-    if (hasHalfFPTextures) {
-        fConfigTable[kAlpha_half_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
-        // ES requires either 3.2 or the combination of EXT_color_buffer_half_float and support for
-        // GL_RED internal format.
-        if (kGL_GrGLStandard == standard || version >= GR_GL_VER(3,2) ||
-            (this->textureRedSupport() &&
-             ctxInfo.hasExtension("GL_EXT_color_buffer_half_float"))) {
-            fConfigTable[kAlpha_half_GrPixelConfig].fFlags |= fpRenderFlags;
-        }
-    }
     if (texStorageSupported) {
         fConfigTable[kAlpha_half_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
     }
@@ -1889,29 +1922,6 @@
     // No sized/unsized internal format distinction for compressed formats, no external format.
     // Below we set the external formats and types to 0.
 
-    fConfigTable[kIndex_8_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_PALETTE8_RGBA8;
-    fConfigTable[kIndex_8_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_PALETTE8_RGBA8;
-    fConfigTable[kIndex_8_GrPixelConfig].fFormats.fExternalFormat[kOther_ExternalFormatUsage] = 0;
-    fConfigTable[kIndex_8_GrPixelConfig].fFormats.fExternalType = 0;
-    fConfigTable[kIndex_8_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
-    // Disable this for now, while we investigate https://bug.skia.org/4333
-    if ((false)) {
-        // Check for 8-bit palette..
-        GrGLint numFormats;
-        GR_GL_GetIntegerv(gli, GR_GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numFormats);
-        if (numFormats) {
-            SkAutoSTMalloc<10, GrGLint> formats(numFormats);
-            GR_GL_GetIntegerv(gli, GR_GL_COMPRESSED_TEXTURE_FORMATS, formats);
-            for (int i = 0; i < numFormats; ++i) {
-                if (GR_GL_PALETTE8_RGBA8 == formats[i]) {
-                    fConfigTable[kIndex_8_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag;
-                    break;
-                }
-            }
-        }
-    }
-    fConfigTable[kIndex_8_GrPixelConfig].fSwizzle = GrSwizzle::RGBA();
-
     // May change the internal format based on extensions.
     fConfigTable[kLATC_GrPixelConfig].fFormats.fBaseInternalFormat =
         GR_GL_COMPRESSED_LUMINANCE_LATC1;
diff --git a/src/gpu/gl/GrGLDefines.h b/src/gpu/gl/GrGLDefines.h
index 18dee28..54b6d74 100644
--- a/src/gpu/gl/GrGLDefines.h
+++ b/src/gpu/gl/GrGLDefines.h
@@ -461,6 +461,9 @@
 #define GR_GL_R32I                           0x8235
 #define GR_GL_R32UI                          0x8236
 
+/* Luminance sized formats */
+#define GR_GL_LUMINANCE8                     0x8040
+
 /* Alpha sized formats */
 #define GR_GL_ALPHA8                         0x803C
 #define GR_GL_ALPHA16                        0x803E
@@ -506,7 +509,6 @@
 /* RGBA sized formats */
 #define GR_GL_RGBA4                          0x8056
 #define GR_GL_RGB5_A1                        0x8057
-#define GR_GL_PALETTE8_RGBA8                 0x8B96
 #define GR_GL_RGBA8                          0x8058
 #define GR_GL_SRGB8_ALPHA8                   0x8C43
 #define GR_GL_RGBA16F                        0x881A
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 577aca8..0e5d856 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -6,31 +6,33 @@
  */
 
 #include "GrGLGpu.h"
+
+#include "../private/GrGLSL.h"
+#include "GrFixedClip.h"
 #include "GrGLBuffer.h"
 #include "GrGLGpuCommandBuffer.h"
 #include "GrGLStencilAttachment.h"
 #include "GrGLTextureRenderTarget.h"
-#include "GrFixedClip.h"
 #include "GrGpuResourcePriv.h"
 #include "GrMesh.h"
-#include "GrPipeline.h"
 #include "GrPLSGeometryProcessor.h"
+#include "GrPipeline.h"
 #include "GrRenderTargetPriv.h"
 #include "GrShaderCaps.h"
 #include "GrSurfacePriv.h"
 #include "GrTexturePriv.h"
 #include "GrTypes.h"
-#include "builders/GrGLShaderStringBuilder.h"
-#include "glsl/GrGLSLPLSPathRendering.h"
-#include "instanced/GLInstancedRendering.h"
+#include "SkAutoMalloc.h"
 #include "SkMakeUnique.h"
 #include "SkMipMap.h"
 #include "SkPixmap.h"
-#include "SkStrokeRec.h"
 #include "SkSLCompiler.h"
+#include "SkStrokeRec.h"
 #include "SkTemplates.h"
 #include "SkTypes.h"
-#include "../private/GrGLSL.h"
+#include "builders/GrGLShaderStringBuilder.h"
+#include "glsl/GrGLSLPLSPathRendering.h"
+#include "instanced/GLInstancedRendering.h"
 
 #define GL_CALL(X) GR_GL_CALL(this->glInterface(), X)
 #define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->glInterface(), RET, X)
@@ -776,7 +778,7 @@
                                    GrPixelConfig srcConfig,
                                    DrawPreference* drawPreference,
                                    WritePixelTempDrawInfo* tempDrawInfo) {
-    if (kIndex_8_GrPixelConfig == srcConfig || GrPixelConfigIsCompressed(dstSurface->config())) {
+    if (GrPixelConfigIsCompressed(dstSurface->config())) {
         return false;
     }
 
@@ -927,6 +929,7 @@
     SkASSERT(!GrPixelConfigIsCompressed(config));
     switch (config) {
         case kAlpha_8_GrPixelConfig:
+        case kGray_8_GrPixelConfig:
             return 1;
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
@@ -1417,10 +1420,6 @@
         return allocate_and_populate_compressed_texture(desc, *interface, caps, target,
                                                         internalFormat, texels, width, height);
     } else {
-        // Paletted textures can't be updated.
-        if (GR_GL_PALETTE8_RGBA8 == internalFormat) {
-            return false;
-        }
         for (int currentMipLevel = 0; currentMipLevel < texels.count(); currentMipLevel++) {
             SkASSERT(texels[currentMipLevel].fPixels || kTransfer_UploadType == uploadType);
 
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index 045593f..c3e81fb 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -14,6 +14,7 @@
 #include "GrShaderCaps.h"
 #include "GrSwizzle.h"
 #include "GrTexture.h"
+#include "SkAutoMalloc.h"
 #include "SkTraceEvent.h"
 #include "gl/GrGLGpu.h"
 #include "gl/GrGLProgram.h"
diff --git a/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp b/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
index df44edd..6c719fe 100644
--- a/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
@@ -6,11 +6,12 @@
  */
 
 #include "GrGLShaderStringBuilder.h"
-#include "gl/GrGLGpu.h"
-#include "gl/GrGLSLPrettyPrint.h"
-#include "SkTraceEvent.h"
+#include "SkAutoMalloc.h"
 #include "SkSLCompiler.h"
 #include "SkSLGLSLCodeGenerator.h"
+#include "SkTraceEvent.h"
+#include "gl/GrGLGpu.h"
+#include "gl/GrGLSLPrettyPrint.h"
 #include "ir/SkSLProgram.h"
 
 #define GL_CALL(X) GR_GL_CALL(gpu->glInterface(), X)
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index 1182996..c805825 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -65,8 +65,7 @@
     if (primProc.getPixelLocalStorageState() !=
         GrPixelLocalStorageState::kDraw_GrPixelLocalStorageState) {
         this->emitAndInstallXferProc(this->pipeline().getXferProcessor(), *inputColor,
-                                     *inputCoverage, this->pipeline().ignoresCoverage(),
-                                     primProc.getPixelLocalStorageState());
+                                     *inputCoverage, primProc.getPixelLocalStorageState());
         this->emitFSOutputSwizzle(this->pipeline().getXferProcessor().hasSecondaryOutput());
     }
 
@@ -206,7 +205,6 @@
 void GrGLSLProgramBuilder::emitAndInstallXferProc(const GrXferProcessor& xp,
                                                   const GrGLSLExpr4& colorIn,
                                                   const GrGLSLExpr4& coverageIn,
-                                                  bool ignoresCoverage,
                                                   GrPixelLocalStorageState plsState) {
     // Program builders have a bit of state we need to clear with each effect
     AutoStageAdvance adv(this);
@@ -237,7 +235,7 @@
                                        this->uniformHandler(),
                                        this->shaderCaps(),
                                        xp, colorIn.c_str(),
-                                       ignoresCoverage ? nullptr : coverageIn.c_str(),
+                                       coverageIn.c_str(),
                                        fFS.getPrimaryColorOutputName(),
                                        fFS.getSecondaryColorOutputName(),
                                        texSamplers.begin(),
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.h b/src/gpu/glsl/GrGLSLProgramBuilder.h
index bfd9f7a..2dba90f 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.h
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.h
@@ -159,7 +159,6 @@
     void emitAndInstallXferProc(const GrXferProcessor&,
                                 const GrGLSLExpr4& colorIn,
                                 const GrGLSLExpr4& coverageIn,
-                                bool ignoresCoverage,
                                 GrPixelLocalStorageState plsState);
     void emitSamplersAndImageStorages(const GrProcessor& processor,
                                       SkTArray<SamplerHandle>* outTexSamplerHandles,
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
index e2cbdee..5609a9c 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
@@ -124,8 +124,9 @@
         GrShaderVar("xform", kMat44f_GrSLType),
     };
     SkString functionBody;
-    // Gamut xform, clamp to destination gamut
-    functionBody.append("\tcolor.rgb = clamp((xform * vec4(color.rgb, 1.0)).rgb, 0.0, 1.0);\n");
+    // Gamut xform, clamp to destination gamut. We only support/have premultiplied textures, so we
+    // always just clamp to alpha.
+    functionBody.append("\tcolor.rgb = clamp((xform * vec4(color.rgb, 1.0)).rgb, 0.0, color.a);\n");
     functionBody.append("\treturn color;");
     SkString colorGamutXformFuncName;
     this->emitFunction(kVec4f_GrSLType,
diff --git a/src/gpu/instanced/GLInstancedRendering.cpp b/src/gpu/instanced/GLInstancedRendering.cpp
index da764b2..0d2a2dd 100644
--- a/src/gpu/instanced/GLInstancedRendering.cpp
+++ b/src/gpu/instanced/GLInstancedRendering.cpp
@@ -60,7 +60,9 @@
     return static_cast<GrGLGpu*>(this->gpu());
 }
 
-sk_sp<InstancedRendering::Op> GLInstancedRendering::makeOp() { return sk_sp<Op>(new GLOp(this)); }
+std::unique_ptr<InstancedRendering::Op> GLInstancedRendering::makeOp() {
+    return std::unique_ptr<Op>(new GLOp(this));
+}
 
 void GLInstancedRendering::onBeginFlush(GrResourceProvider* rp) {
     // Count what there is to draw.
diff --git a/src/gpu/instanced/GLInstancedRendering.h b/src/gpu/instanced/GLInstancedRendering.h
index c77b61d..0088217 100644
--- a/src/gpu/instanced/GLInstancedRendering.h
+++ b/src/gpu/instanced/GLInstancedRendering.h
@@ -33,7 +33,7 @@
 
     GrGLGpu* glGpu() const;
 
-    sk_sp<Op> makeOp() override;
+    std::unique_ptr<Op> makeOp() override;
 
     void onBeginFlush(GrResourceProvider*) override;
     void onDraw(const GrPipeline&, const InstanceProcessor&, const Op*) override;
diff --git a/src/gpu/instanced/InstanceProcessor.cpp b/src/gpu/instanced/InstanceProcessor.cpp
index e890247..8626eb9 100644
--- a/src/gpu/instanced/InstanceProcessor.cpp
+++ b/src/gpu/instanced/InstanceProcessor.cpp
@@ -354,8 +354,7 @@
     }
     SkASSERT(!(usedShapeDefinitions & (kRect_ShapeFlag | kComplexRRect_ShapeFlag)));
 
-    backend->emitCode(v, f, pipeline.ignoresCoverage() ? nullptr : args.fOutputCoverage,
-                      args.fOutputColor);
+    backend->emitCode(v, f, args.fOutputCoverage, args.fOutputColor);
 
     const char* localCoords = nullptr;
     if (ip.opInfo().fUsesLocalCoords) {
diff --git a/src/gpu/instanced/InstancedRendering.cpp b/src/gpu/instanced/InstancedRendering.cpp
index 08fdf3e..52a48cb 100644
--- a/src/gpu/instanced/InstancedRendering.cpp
+++ b/src/gpu/instanced/InstancedRendering.cpp
@@ -21,30 +21,33 @@
       fDrawPool(1024, 1024) {
 }
 
-sk_sp<GrDrawOp> InstancedRendering::recordRect(const SkRect& rect, const SkMatrix& viewMatrix,
-                                               GrColor color, GrAA aa,
-                                               const GrInstancedPipelineInfo& info,
-                                               GrAAType* aaType) {
+std::unique_ptr<GrDrawOp> InstancedRendering::recordRect(const SkRect& rect,
+                                                         const SkMatrix& viewMatrix, GrColor color,
+                                                         GrAA aa,
+                                                         const GrInstancedPipelineInfo& info,
+                                                         GrAAType* aaType) {
     return this->recordShape(ShapeType::kRect, rect, viewMatrix, color, rect, aa, info, aaType);
 }
 
-sk_sp<GrDrawOp> InstancedRendering::recordRect(const SkRect& rect, const SkMatrix& viewMatrix,
-                                               GrColor color, const SkRect& localRect, GrAA aa,
-                                               const GrInstancedPipelineInfo& info,
-                                               GrAAType* aaType) {
+std::unique_ptr<GrDrawOp> InstancedRendering::recordRect(const SkRect& rect,
+                                                         const SkMatrix& viewMatrix, GrColor color,
+                                                         const SkRect& localRect, GrAA aa,
+                                                         const GrInstancedPipelineInfo& info,
+                                                         GrAAType* aaType) {
     return this->recordShape(ShapeType::kRect, rect, viewMatrix, color, localRect, aa, info,
                              aaType);
 }
 
-sk_sp<GrDrawOp> InstancedRendering::recordRect(const SkRect& rect, const SkMatrix& viewMatrix,
-                                               GrColor color, const SkMatrix& localMatrix, GrAA aa,
-                                               const GrInstancedPipelineInfo& info,
-                                               GrAAType* aaType) {
+std::unique_ptr<GrDrawOp> InstancedRendering::recordRect(const SkRect& rect,
+                                                         const SkMatrix& viewMatrix, GrColor color,
+                                                         const SkMatrix& localMatrix, GrAA aa,
+                                                         const GrInstancedPipelineInfo& info,
+                                                         GrAAType* aaType) {
     if (localMatrix.hasPerspective()) {
         return nullptr; // Perspective is not yet supported in the local matrix.
     }
-    if (sk_sp<Op> op = this->recordShape(ShapeType::kRect, rect, viewMatrix, color, rect, aa, info,
-                                         aaType)) {
+    if (std::unique_ptr<Op> op = this->recordShape(ShapeType::kRect, rect, viewMatrix, color, rect,
+                                                   aa, info, aaType)) {
         op->getSingleInstance().fInfo |= kLocalMatrix_InfoFlag;
         op->appendParamsTexel(localMatrix.getScaleX(), localMatrix.getSkewX(),
                               localMatrix.getTranslateX());
@@ -56,37 +59,40 @@
     return nullptr;
 }
 
-sk_sp<GrDrawOp> InstancedRendering::recordOval(const SkRect& oval, const SkMatrix& viewMatrix,
-                                               GrColor color, GrAA aa,
-                                               const GrInstancedPipelineInfo& info,
-                                               GrAAType* aaType) {
+std::unique_ptr<GrDrawOp> InstancedRendering::recordOval(const SkRect& oval,
+                                                         const SkMatrix& viewMatrix, GrColor color,
+                                                         GrAA aa,
+                                                         const GrInstancedPipelineInfo& info,
+                                                         GrAAType* aaType) {
     return this->recordShape(ShapeType::kOval, oval, viewMatrix, color, oval, aa, info, aaType);
 }
 
-sk_sp<GrDrawOp> InstancedRendering::recordRRect(const SkRRect& rrect, const SkMatrix& viewMatrix,
-                                                GrColor color, GrAA aa,
-                                                const GrInstancedPipelineInfo& info,
-                                                GrAAType* aaType) {
-    if (sk_sp<Op> op = this->recordShape(GetRRectShapeType(rrect), rrect.rect(), viewMatrix, color,
-                                         rrect.rect(), aa, info, aaType)) {
+std::unique_ptr<GrDrawOp> InstancedRendering::recordRRect(const SkRRect& rrect,
+                                                          const SkMatrix& viewMatrix, GrColor color,
+                                                          GrAA aa,
+                                                          const GrInstancedPipelineInfo& info,
+                                                          GrAAType* aaType) {
+    if (std::unique_ptr<Op> op =
+                this->recordShape(GetRRectShapeType(rrect), rrect.rect(), viewMatrix, color,
+                                  rrect.rect(), aa, info, aaType)) {
         op->appendRRectParams(rrect);
         return std::move(op);
     }
     return nullptr;
 }
 
-sk_sp<GrDrawOp> InstancedRendering::recordDRRect(const SkRRect& outer, const SkRRect& inner,
-                                                 const SkMatrix& viewMatrix, GrColor color, GrAA aa,
-                                                 const GrInstancedPipelineInfo& info,
-                                                 GrAAType* aaType) {
+std::unique_ptr<GrDrawOp> InstancedRendering::recordDRRect(
+        const SkRRect& outer, const SkRRect& inner, const SkMatrix& viewMatrix, GrColor color,
+        GrAA aa, const GrInstancedPipelineInfo& info, GrAAType* aaType) {
     if (inner.getType() > SkRRect::kSimple_Type) {
        return nullptr; // Complex inner round rects are not yet supported.
     }
     if (SkRRect::kEmpty_Type == inner.getType()) {
         return this->recordRRect(outer, viewMatrix, color, aa, info, aaType);
     }
-    if (sk_sp<Op> op = this->recordShape(GetRRectShapeType(outer), outer.rect(), viewMatrix, color,
-                                         outer.rect(), aa, info, aaType)) {
+    if (std::unique_ptr<Op> op =
+                this->recordShape(GetRRectShapeType(outer), outer.rect(), viewMatrix, color,
+                                  outer.rect(), aa, info, aaType)) {
         op->appendRRectParams(outer);
         ShapeType innerShapeType = GetRRectShapeType(inner);
         op->fInfo.fInnerShapeTypes |= GetShapeFlag(innerShapeType);
@@ -98,7 +104,7 @@
     return nullptr;
 }
 
-sk_sp<InstancedRendering::Op> InstancedRendering::recordShape(
+std::unique_ptr<InstancedRendering::Op> InstancedRendering::recordShape(
         ShapeType type, const SkRect& bounds, const SkMatrix& viewMatrix, GrColor color,
         const SkRect& localRect, GrAA aa, const GrInstancedPipelineInfo& info, GrAAType* aaType) {
     SkASSERT(State::kRecordingDraws == fState);
@@ -112,7 +118,7 @@
         return nullptr;
     }
 
-    sk_sp<Op> op = this->makeOp();
+    std::unique_ptr<Op> op = this->makeOp();
     op->fInfo.fAntialiasMode = antialiasMode;
     op->fInfo.fShapeTypes = GetShapeFlag(type);
     op->fInfo.fCannotDiscard = !info.fCanDiscard;
diff --git a/src/gpu/instanced/InstancedRendering.h b/src/gpu/instanced/InstancedRendering.h
index 20d0617..1defcc7 100644
--- a/src/gpu/instanced/InstancedRendering.h
+++ b/src/gpu/instanced/InstancedRendering.h
@@ -45,27 +45,36 @@
      * this class before attempting to flush ops returned by it. It is invalid to record new
      * draws between beginFlush() and endFlush().
      */
-    sk_sp<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&, GrColor, GrAA,
-                                                     const GrInstancedPipelineInfo&, GrAAType*);
+    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&,
+                                                               GrColor, GrAA,
+                                                               const GrInstancedPipelineInfo&,
+                                                               GrAAType*);
 
-    sk_sp<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&, GrColor,
-                                                     const SkRect& localRect, GrAA,
-                                                     const GrInstancedPipelineInfo&, GrAAType*);
+    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&,
+                                                               GrColor, const SkRect& localRect,
+                                                               GrAA, const GrInstancedPipelineInfo&,
+                                                               GrAAType*);
 
-    sk_sp<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&, GrColor,
-                                                     const SkMatrix& localMatrix, GrAA,
-                                                     const GrInstancedPipelineInfo&, GrAAType*);
+    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&,
+                                                               GrColor, const SkMatrix& localMatrix,
+                                                               GrAA, const GrInstancedPipelineInfo&,
+                                                               GrAAType*);
 
-    sk_sp<GrDrawOp> SK_WARN_UNUSED_RESULT recordOval(const SkRect&, const SkMatrix&, GrColor, GrAA,
-                                                     const GrInstancedPipelineInfo&, GrAAType*);
+    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordOval(const SkRect&, const SkMatrix&,
+                                                               GrColor, GrAA,
+                                                               const GrInstancedPipelineInfo&,
+                                                               GrAAType*);
 
-    sk_sp<GrDrawOp> SK_WARN_UNUSED_RESULT recordRRect(const SkRRect&, const SkMatrix&, GrColor,
-                                                      GrAA, const GrInstancedPipelineInfo&,
-                                                      GrAAType*);
+    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordRRect(const SkRRect&, const SkMatrix&,
+                                                                GrColor, GrAA,
+                                                                const GrInstancedPipelineInfo&,
+                                                                GrAAType*);
 
-    sk_sp<GrDrawOp> SK_WARN_UNUSED_RESULT recordDRRect(const SkRRect& outer, const SkRRect& inner,
-                                                       const SkMatrix&, GrColor, GrAA,
-                                                       const GrInstancedPipelineInfo&, GrAAType*);
+    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordDRRect(const SkRRect& outer,
+                                                                 const SkRRect& inner,
+                                                                 const SkMatrix&, GrColor, GrAA,
+                                                                 const GrInstancedPipelineInfo&,
+                                                                 GrAAType*);
 
     /**
      * Compiles all recorded draws into GPU buffers and allows the client to begin flushing the
@@ -175,15 +184,16 @@
         kFlushing
     };
 
-    sk_sp<Op> SK_WARN_UNUSED_RESULT recordShape(ShapeType, const SkRect& bounds,
-                                                const SkMatrix& viewMatrix, GrColor,
-                                                const SkRect& localRect, GrAA aa,
-                                                const GrInstancedPipelineInfo&, GrAAType*);
+    std::unique_ptr<Op> SK_WARN_UNUSED_RESULT recordShape(ShapeType, const SkRect& bounds,
+                                                          const SkMatrix& viewMatrix, GrColor,
+                                                          const SkRect& localRect, GrAA aa,
+                                                          const GrInstancedPipelineInfo&,
+                                                          GrAAType*);
 
     bool selectAntialiasMode(const SkMatrix& viewMatrix, GrAA aa, const GrInstancedPipelineInfo&,
                              GrAAType*, AntialiasMode*);
 
-    virtual sk_sp<Op> makeOp() = 0;
+    virtual std::unique_ptr<Op> makeOp() = 0;
 
     const sk_sp<GrGpu> fGpu;
     State fState;
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index 2d25ff5..e845e5e 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -538,14 +538,12 @@
     const Attribute* inPosition() const { return fInPosition; }
     const Attribute* inQuadEdge() const { return fInQuadEdge; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
 
     class GLSLProcessor : public GrGLSLGeometryProcessor {
     public:
-        GLSLProcessor()
-            : fColor(GrColor_ILLEGAL) {}
+        GLSLProcessor() : fColor(GrColor_ILLEGAL) {}
 
         void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
             const QuadEdgeEffect& qe = args.fGP.cast<QuadEdgeEffect>();
@@ -562,10 +560,8 @@
 
             GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
             // Setup pass through color
-            if (!qe.colorIgnored()) {
-                this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor,
-                                        &fColorUniform);
-            }
+            this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor,
+                                    &fColorUniform);
 
             // Setup position
             this->setupPosition(vertBuilder, gpArgs, qe.inPosition()->fName);
@@ -604,10 +600,7 @@
                                   const GrShaderCaps&,
                                   GrProcessorKeyBuilder* b) {
             const QuadEdgeEffect& qee = gp.cast<QuadEdgeEffect>();
-            uint32_t key = 0;
-            key |= qee.usesLocalCoords() && qee.localMatrix().hasPerspective() ? 0x1 : 0x0;
-            key |= qee.colorIgnored() ? 0x2 : 0x0;
-            b->add32(key);
+            b->add32(SkToBool(qee.usesLocalCoords() && qee.localMatrix().hasPerspective()));
         }
 
         void setData(const GrGLSLProgramDataManager& pdman,
@@ -712,31 +705,26 @@
 
 static sk_sp<GrGeometryProcessor> create_fill_gp(bool tweakAlphaForCoverage,
                                                  const SkMatrix& viewMatrix,
-                                                 bool usesLocalCoords,
-                                                 bool coverageIgnored) {
+                                                 bool usesLocalCoords) {
     using namespace GrDefaultGeoProcFactory;
 
-    Color color(Color::kAttribute_Type);
     Coverage::Type coverageType;
-    // TODO remove coverage if coverage is ignored
-    /*if (coverageIgnored) {
-        coverageType = Coverage::kNone_Type;
-    } else*/ if (tweakAlphaForCoverage) {
+    if (tweakAlphaForCoverage) {
         coverageType = Coverage::kSolid_Type;
     } else {
         coverageType = Coverage::kAttribute_Type;
     }
-    Coverage coverage(coverageType);
-    LocalCoords localCoords(usesLocalCoords ? LocalCoords::kUsePosition_Type :
-                                              LocalCoords::kUnused_Type);
-    return MakeForDeviceSpace(color, coverage, localCoords, viewMatrix);
+    LocalCoords::Type localCoordsType =
+            usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
+    return MakeForDeviceSpace(Color::kAttribute_Type, coverageType, localCoordsType, viewMatrix);
 }
 
 class AAConvexPathOp final : public GrMeshDrawOp {
 public:
     DEFINE_OP_CLASS_ID
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkPath& path) {
-        return sk_sp<GrDrawOp>(new AAConvexPathOp(color, viewMatrix, path));
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix,
+                                          const SkPath& path) {
+        return std::unique_ptr<GrDrawOp>(new AAConvexPathOp(color, viewMatrix, path));
     }
 
     const char* name() const override { return "AAConvexPathOp"; }
@@ -762,13 +750,9 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fColor);
 
         fUsesLocalCoords = optimizations.readsLocalCoords();
-        fCoverageIgnored = !optimizations.readsCoverage();
         fLinesOnly = SkPath::kLine_SegmentMask == fPaths[0].fPath.getSegmentMasks();
         fCanTweakAlphaForCoverage = optimizations.canTweakAlphaForCoverage();
     }
@@ -777,10 +761,8 @@
         bool canTweakAlphaForCoverage = this->canTweakAlphaForCoverage();
 
         // Setup GrGeometryProcessor
-        sk_sp<GrGeometryProcessor> gp(create_fill_gp(canTweakAlphaForCoverage,
-                                                     this->viewMatrix(),
-                                                     this->usesLocalCoords(),
-                                                     this->coverageIgnored()));
+        sk_sp<GrGeometryProcessor> gp(create_fill_gp(
+                canTweakAlphaForCoverage, this->viewMatrix(), this->usesLocalCoords()));
         if (!gp) {
             SkDebugf("Could not create GrGeometryProcessor\n");
             return;
@@ -962,11 +944,9 @@
     bool usesLocalCoords() const { return fUsesLocalCoords; }
     bool canTweakAlphaForCoverage() const { return fCanTweakAlphaForCoverage; }
     const SkMatrix& viewMatrix() const { return fPaths[0].fViewMatrix; }
-    bool coverageIgnored() const { return fCoverageIgnored; }
 
     GrColor fColor;
     bool fUsesLocalCoords;
-    bool fCoverageIgnored;
     bool fLinesOnly;
     bool fCanTweakAlphaForCoverage;
 
@@ -989,9 +969,10 @@
     SkPath path;
     args.fShape->asPath(&path);
 
-    sk_sp<GrDrawOp> op = AAConvexPathOp::Make(args.fPaint->getColor(), *args.fViewMatrix, path);
+    std::unique_ptr<GrDrawOp> op =
+            AAConvexPathOp::Make(args.fPaint.getColor(), *args.fViewMatrix, path);
 
-    GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fAAType);
+    GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
     pipelineBuilder.setUserStencil(args.fUserStencilSettings);
 
     args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(op));
diff --git a/src/gpu/ops/GrAADistanceFieldPathRenderer.cpp b/src/gpu/ops/GrAADistanceFieldPathRenderer.cpp
index b97d35e..d025c94 100644
--- a/src/gpu/ops/GrAADistanceFieldPathRenderer.cpp
+++ b/src/gpu/ops/GrAADistanceFieldPathRenderer.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright 2014 Google Inc.
+ * Copyright 2017 ARM Ltd.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
@@ -19,7 +20,10 @@
 #include "effects/GrDistanceFieldGeoProc.h"
 #include "ops/GrMeshDrawOp.h"
 
+#include "SkPathOps.h"
+#include "SkAutoMalloc.h"
 #include "SkDistanceFieldGen.h"
+#include "GrDistanceFieldGenFromVector.h"
 
 #define ATLAS_TEXTURE_WIDTH 2048
 #define ATLAS_TEXTURE_HEIGHT 2048
@@ -35,6 +39,7 @@
 #endif
 
 // mip levels
+static const int kMinSize = 16;
 static const int kSmallMIP = 32;
 static const int kMediumMIP = 73;
 static const int kLargeMIP = 162;
@@ -103,14 +108,17 @@
         return false;
     }
 
-    // only support paths with bounds within kMediumMIP by kMediumMIP,
-    // scaled to have bounds within 2.0f*kLargeMIP by 2.0f*kLargeMIP
-    // the goal is to accelerate rendering of lots of small paths that may be scaling
+    // Only support paths with bounds within kMediumMIP by kMediumMIP,
+    // scaled to have bounds within 2.0f*kLargeMIP by 2.0f*kLargeMIP.
+    // For clarity, the original or scaled path should be at least kMinSize by kMinSize.
+    // TODO: revisit this last criteria with Joel's patch.
+    // The goal is to accelerate rendering of lots of small paths that may be scaling.
     SkScalar maxScale = args.fViewMatrix->getMaxScale();
     SkRect bounds = args.fShape->styledBounds();
     SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
 
-    return maxDim <= kMediumMIP && maxDim * maxScale <= 2.0f*kLargeMIP;
+    return maxDim <= kMediumMIP &&
+           maxDim * maxScale >= kMinSize && maxDim * maxScale <= 2.0f*kLargeMIP;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -126,11 +134,12 @@
     using ShapeCache = SkTDynamicHash<ShapeData, ShapeData::Key>;
     using ShapeDataList = GrAADistanceFieldPathRenderer::ShapeDataList;
 
-    static sk_sp<GrDrawOp> Make(GrColor color, const GrShape& shape, const SkMatrix& viewMatrix,
-                                GrDrawOpAtlas* atlas, ShapeCache* shapeCache,
-                                ShapeDataList* shapeList, bool gammaCorrect) {
-        return sk_sp<GrDrawOp>(new AADistanceFieldPathOp(color, shape, viewMatrix, atlas,
-                                                         shapeCache, shapeList, gammaCorrect));
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const GrShape& shape,
+                                          const SkMatrix& viewMatrix, GrDrawOpAtlas* atlas,
+                                          ShapeCache* shapeCache, ShapeDataList* shapeList,
+                                          bool gammaCorrect) {
+        return std::unique_ptr<GrDrawOp>(new AADistanceFieldPathOp(
+                color, shape, viewMatrix, atlas, shapeCache, shapeList, gammaCorrect));
     }
 
     const char* name() const override { return "AADistanceFieldPathOp"; }
@@ -169,14 +178,8 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fShapes[0].fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fShapes[0].fColor);
-
-        fColorIgnored = !optimizations.readsColor();
         fUsesLocalCoords = optimizations.readsLocalCoords();
-        fCoverageIgnored = !optimizations.readsCoverage();
     }
 
     struct FlushInfo {
@@ -332,45 +335,56 @@
         drawMatrix.setScale(scale, scale);
         drawMatrix.postTranslate(intPad - dx, intPad - dy);
 
-        // setup bitmap backing
         SkASSERT(devPathBounds.fLeft == 0);
         SkASSERT(devPathBounds.fTop == 0);
-        SkAutoPixmapStorage dst;
-        if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
-                                              devPathBounds.height()))) {
-            return false;
-        }
-        sk_bzero(dst.writable_addr(), dst.getSafeSize());
 
-        // rasterize path
-        SkPaint paint;
-        paint.setStyle(SkPaint::kFill_Style);
-        paint.setAntiAlias(true);
-
-        SkDraw draw;
-        sk_bzero(&draw, sizeof(draw));
-
-        SkRasterClip rasterClip;
-        rasterClip.setRect(devPathBounds);
-        draw.fRC = &rasterClip;
-        draw.fMatrix = &drawMatrix;
-        draw.fDst = dst;
-
-        SkPath path;
-        shape.asPath(&path);
-        draw.drawPathCoverage(path, paint);
-
-        // generate signed distance field
-        devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
-        width = devPathBounds.width();
-        height = devPathBounds.height();
+        // setup signed distance field storage
+        SkIRect dfBounds = devPathBounds.makeOutset(SK_DistanceFieldPad, SK_DistanceFieldPad);
+        width = dfBounds.width();
+        height = dfBounds.height();
         // TODO We should really generate this directly into the plot somehow
         SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
 
-        // Generate signed distance field
-        SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
-                                           (const unsigned char*)dst.addr(),
-                                           dst.width(), dst.height(), dst.rowBytes());
+        SkPath path;
+        shape.asPath(&path);
+#ifndef SK_USE_LEGACY_DISTANCE_FIELDS
+        // Generate signed distance field directly from SkPath
+        bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dfStorage.get(),
+                                        path, drawMatrix,
+                                        width, height, width * sizeof(unsigned char));
+        if (!succeed) {
+#endif
+            // setup bitmap backing
+            SkAutoPixmapStorage dst;
+            if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
+                                                  devPathBounds.height()))) {
+                return false;
+            }
+            sk_bzero(dst.writable_addr(), dst.getSafeSize());
+
+            // rasterize path
+            SkPaint paint;
+            paint.setStyle(SkPaint::kFill_Style);
+            paint.setAntiAlias(true);
+
+            SkDraw draw;
+            sk_bzero(&draw, sizeof(draw));
+
+            SkRasterClip rasterClip;
+            rasterClip.setRect(devPathBounds);
+            draw.fRC = &rasterClip;
+            draw.fMatrix = &drawMatrix;
+            draw.fDst = dst;
+
+            draw.drawPathCoverage(path, paint);
+
+            // Generate signed distance field
+            SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
+                                               (const unsigned char*)dst.addr(),
+                                               dst.width(), dst.height(), dst.rowBytes());
+#ifndef SK_USE_LEGACY_DISTANCE_FIELDS
+        }
+#endif
 
         // add to atlas
         SkIPoint16 atlasLocation;
@@ -389,29 +403,12 @@
         // set the bounds rect to the original bounds
         shapeData->fBounds = bounds;
 
-        // set up texture coordinates
-        SkScalar texLeft = bounds.fLeft;
-        SkScalar texTop = bounds.fTop;
-        SkScalar texRight = bounds.fRight;
-        SkScalar texBottom = bounds.fBottom;
-
-        // transform original path's bounds to texture space
-        texLeft *= scale;
-        texTop *= scale;
-        texRight *= scale;
-        texBottom *= scale;
+        // set up path to texture coordinate transform
+        shapeData->fScale = scale;
         dx -= SK_DistanceFieldPad + kAntiAliasPad;
         dy -= SK_DistanceFieldPad + kAntiAliasPad;
-        texLeft += atlasLocation.fX - dx;
-        texTop += atlasLocation.fY - dy;
-        texRight += atlasLocation.fX - dx;
-        texBottom += atlasLocation.fY - dy;
-
-        GrTexture* texture = atlas->getTexture();
-        shapeData->fTexCoords.setLTRB(texLeft / texture->width(),
-                                      texTop / texture->height(),
-                                      texRight / texture->width(),
-                                      texBottom / texture->height());
+        shapeData->fTranslate.fX = atlasLocation.fX - dx;
+        shapeData->fTranslate.fY = atlasLocation.fY - dy;
 
         fShapeCache->add(shapeData);
         fShapeList->addToTail(shapeData);
@@ -430,10 +427,15 @@
                            const ShapeData* shapeData) const {
         SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
 
+        // outset bounds to include ~1 pixel of AA in device space
+        SkRect bounds = shapeData->fBounds;
+        SkScalar outset = SkScalarInvert(maxScale);
+        bounds.outset(outset, outset);
+
         // vertex positions
         // TODO make the vertex attributes a struct
-        positions->setRectFan(shapeData->fBounds.left(), shapeData->fBounds.top(),
-                              shapeData->fBounds.right(), shapeData->fBounds.bottom(), vertexStride);
+        positions->setRectFan(bounds.left(), bounds.top(), bounds.right(), bounds.bottom(),
+                              vertexStride);
 
         // colors
         for (int i = 0; i < kVerticesPerQuad; i++) {
@@ -441,11 +443,32 @@
             *colorPtr = color;
         }
 
+        // set up texture coordinates
+        SkScalar texLeft = bounds.fLeft;
+        SkScalar texTop = bounds.fTop;
+        SkScalar texRight = bounds.fRight;
+        SkScalar texBottom = bounds.fBottom;
+
+        // transform original path's bounds to texture space
+        SkScalar scale = shapeData->fScale;
+        const SkVector& translate = shapeData->fTranslate;
+        texLeft *= scale;
+        texTop *= scale;
+        texRight *= scale;
+        texBottom *= scale;
+        texLeft += translate.fX;
+        texTop += translate.fY;
+        texRight += translate.fX;
+        texBottom += translate.fY;
+
         // vertex texture coords
         // TODO make these int16_t
         SkPoint* textureCoords = (SkPoint*)(offset + sizeof(SkPoint) + sizeof(GrColor));
-        textureCoords->setRectFan(shapeData->fTexCoords.left(), shapeData->fTexCoords.top(),
-                                  shapeData->fTexCoords.right(), shapeData->fTexCoords.bottom(),
+        GrTexture* texture = atlas->getTexture();
+        textureCoords->setRectFan(texLeft / texture->width(),
+                                  texTop / texture->height(),
+                                  texRight / texture->width(),
+                                  texBottom / texture->height(),
                                   vertexStride);
     }
 
@@ -486,8 +509,6 @@
 
     SkMatrix fViewMatrix;
     bool fUsesLocalCoords;
-    bool fColorIgnored;
-    bool fCoverageIgnored;
 
     struct Entry {
         GrColor fColor;
@@ -521,10 +542,10 @@
         }
     }
 
-    sk_sp<GrDrawOp> op = AADistanceFieldPathOp::Make(args.fPaint->getColor(), *args.fShape,
-                                                     *args.fViewMatrix, fAtlas.get(), &fShapeCache,
-                                                     &fShapeList, args.fGammaCorrect);
-    GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fAAType);
+    std::unique_ptr<GrDrawOp> op = AADistanceFieldPathOp::Make(
+            args.fPaint.getColor(), *args.fShape, *args.fViewMatrix, fAtlas.get(), &fShapeCache,
+            &fShapeList, args.fGammaCorrect);
+    GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
     pipelineBuilder.setUserStencil(args.fUserStencilSettings);
 
     args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(op));
diff --git a/src/gpu/ops/GrAADistanceFieldPathRenderer.h b/src/gpu/ops/GrAADistanceFieldPathRenderer.h
index 5d34807..202b114 100644
--- a/src/gpu/ops/GrAADistanceFieldPathRenderer.h
+++ b/src/gpu/ops/GrAADistanceFieldPathRenderer.h
@@ -71,8 +71,9 @@
         };
         Key fKey;
         GrDrawOpAtlas::AtlasID fID;
-        SkRect fBounds;
-        SkRect fTexCoords;
+        SkRect   fBounds;
+        SkScalar fScale;
+        SkVector fTranslate;
         SK_DECLARE_INTERNAL_LLIST_INTERFACE(ShapeData);
 
         static inline const Key& GetKey(const ShapeData& data) {
diff --git a/src/gpu/ops/GrAAFillRectOp.cpp b/src/gpu/ops/GrAAFillRectOp.cpp
index 0158d60..5c585bf 100644
--- a/src/gpu/ops/GrAAFillRectOp.cpp
+++ b/src/gpu/ops/GrAAFillRectOp.cpp
@@ -52,7 +52,7 @@
                                            const SkMatrix& viewMatrix,
                                            const SkRect& rect,
                                            const SkRect& devRect,
-                                           const GrPipelineOptimizations& optimizations,
+                                           bool tweakAlphaForCoverage,
                                            const SkMatrix* localMatrix) {
     SkPoint* fan0Pos = reinterpret_cast<SkPoint*>(verts);
     SkPoint* fan1Pos = reinterpret_cast<SkPoint*>(verts + 4 * vertexStride);
@@ -115,8 +115,6 @@
         localCoordMatrix.mapPointsWithStride(fan0Loc, fan0Pos, vertexStride, 8);
     }
 
-    bool tweakAlphaForCoverage = optimizations.canTweakAlphaForCoverage();
-
     // Make verts point to vertex color and then set all the color and coverage vertex attrs
     // values.
     verts += sizeof(SkPoint);
@@ -200,7 +198,8 @@
         if (optimizations.getOverrideColorIfSet(&color)) {
             this->first()->setColor(color);
         }
-        fOptimizations = optimizations;
+        fCanTweakAlphaForCoverage = optimizations.canTweakAlphaForCoverage();
+        fNeedsLocalCoords = optimizations.readsLocalCoords();
     }
 
 private:
@@ -210,21 +209,19 @@
     }
 
     void onPrepareDraws(Target* target) const override {
-        bool needLocalCoords = fOptimizations.readsLocalCoords();
         using namespace GrDefaultGeoProcFactory;
 
         Color color(Color::kAttribute_Type);
         Coverage::Type coverageType;
-        if (fOptimizations.canTweakAlphaForCoverage()) {
+        if (fCanTweakAlphaForCoverage) {
             coverageType = Coverage::kSolid_Type;
         } else {
             coverageType = Coverage::kAttribute_Type;
         }
-        Coverage coverage(coverageType);
         LocalCoords lc =
-                needLocalCoords ? LocalCoords::kHasExplicit_Type : LocalCoords::kUnused_Type;
+                fNeedsLocalCoords ? LocalCoords::kHasExplicit_Type : LocalCoords::kUnused_Type;
         sk_sp<GrGeometryProcessor> gp =
-                GrDefaultGeoProcFactory::Make(color, coverage, lc, SkMatrix::I());
+                GrDefaultGeoProcFactory::Make(color, coverageType, lc, SkMatrix::I());
         if (!gp) {
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
@@ -247,7 +244,7 @@
         for (int i = 0; i < fRectCnt; i++) {
             intptr_t verts =
                     reinterpret_cast<intptr_t>(vertices) + i * kVertsPerAAFillRect * vertexStride;
-            if (needLocalCoords) {
+            if (fNeedsLocalCoords) {
                 if (info->hasLocalMatrix()) {
                     localMatrix = &static_cast<const RectWithLocalMatrixInfo*>(info)->localMatrix();
                 } else {
@@ -255,7 +252,7 @@
                 }
             }
             generate_aa_fill_rect_geometry(verts, vertexStride, info->color(), info->viewMatrix(),
-                                           info->rect(), info->devRect(), fOptimizations,
+                                           info->rect(), info->devRect(), fCanTweakAlphaForCoverage,
                                            localMatrix);
             info = this->next(info);
         }
@@ -269,11 +266,12 @@
             return false;
         }
 
+        SkASSERT(fNeedsLocalCoords == that->fNeedsLocalCoords);
+
         // In the event of two ops, one who can tweak, one who cannot, we just fall back to not
         // tweaking.
-        if (fOptimizations.canTweakAlphaForCoverage() &&
-            !that->fOptimizations.canTweakAlphaForCoverage()) {
-            fOptimizations = that->fOptimizations;
+        if (fCanTweakAlphaForCoverage && !that->fCanTweakAlphaForCoverage) {
+            fCanTweakAlphaForCoverage = false;
         }
 
         fRectData.push_back_n(that->fRectData.count(), that->fRectData.begin());
@@ -334,7 +332,8 @@
         return reinterpret_cast<const RectInfo*>(next);
     }
 
-    GrPipelineOptimizations fOptimizations;
+    bool fNeedsLocalCoords;
+    bool fCanTweakAlphaForCoverage;
     SkSTArray<4 * sizeof(RectWithLocalMatrixInfo), uint8_t, true> fRectData;
     int fRectCnt;
 
@@ -343,34 +342,35 @@
 
 namespace GrAAFillRectOp {
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect,
-                     const SkRect& devRect) {
-    return sk_sp<GrDrawOp>(new AAFillRectOp(color, viewMatrix, rect, devRect, nullptr));
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkRect& devRect) {
+    return std::unique_ptr<GrDrawOp>(new AAFillRectOp(color, viewMatrix, rect, devRect, nullptr));
 }
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkMatrix& localMatrix,
-                     const SkRect& rect,
-                     const SkRect& devRect) {
-    return sk_sp<GrDrawOp>(new AAFillRectOp(color, viewMatrix, rect, devRect, &localMatrix));
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkMatrix& localMatrix,
+                               const SkRect& rect,
+                               const SkRect& devRect) {
+    return std::unique_ptr<GrDrawOp>(
+            new AAFillRectOp(color, viewMatrix, rect, devRect, &localMatrix));
 }
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkMatrix& localMatrix,
-                     const SkRect& rect) {
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkMatrix& localMatrix,
+                               const SkRect& rect) {
     SkRect devRect;
     viewMatrix.mapRect(&devRect, rect);
     return Make(color, viewMatrix, localMatrix, rect, devRect);
 }
 
-sk_sp<GrDrawOp> MakeWithLocalRect(GrColor color,
-                                  const SkMatrix& viewMatrix,
-                                  const SkRect& rect,
-                                  const SkRect& localRect) {
+std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrColor color,
+                                            const SkMatrix& viewMatrix,
+                                            const SkRect& rect,
+                                            const SkRect& localRect) {
     SkRect devRect;
     viewMatrix.mapRect(&devRect, rect);
     SkMatrix localMatrix;
diff --git a/src/gpu/ops/GrAAFillRectOp.h b/src/gpu/ops/GrAAFillRectOp.h
index b611ff9..ff9844e 100644
--- a/src/gpu/ops/GrAAFillRectOp.h
+++ b/src/gpu/ops/GrAAFillRectOp.h
@@ -16,26 +16,26 @@
 struct SkRect;
 
 namespace GrAAFillRectOp {
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect,
-                     const SkRect& devRect);
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkRect& devRect);
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkMatrix& localMatrix,
-                     const SkRect& rect);
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkMatrix& localMatrix,
+                               const SkRect& rect);
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkMatrix& localMatrix,
-                     const SkRect& rect,
-                     const SkRect& devRect);
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkMatrix& localMatrix,
+                               const SkRect& rect,
+                               const SkRect& devRect);
 
-sk_sp<GrDrawOp> MakeWithLocalRect(GrColor color,
-                                  const SkMatrix& viewMatrix,
-                                  const SkRect& rect,
-                                  const SkRect& localRect);
+std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrColor color,
+                                            const SkMatrix& viewMatrix,
+                                            const SkRect& rect,
+                                            const SkRect& localRect);
 };
 
 #endif
diff --git a/src/gpu/ops/GrAAHairLinePathRenderer.cpp b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
index affef55..958eb57 100644
--- a/src/gpu/ops/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
@@ -679,18 +679,18 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrDrawOp> Make(GrColor color,
-                                const SkMatrix& viewMatrix,
-                                const SkPath& path,
-                                const GrStyle& style,
-                                const SkIRect& devClipBounds) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color,
+                                          const SkMatrix& viewMatrix,
+                                          const SkPath& path,
+                                          const GrStyle& style,
+                                          const SkIRect& devClipBounds) {
         SkScalar hairlineCoverage;
         uint8_t newCoverage = 0xff;
         if (GrPathRenderer::IsStrokeHairlineOrEquivalent(style, viewMatrix, &hairlineCoverage)) {
             newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
         }
 
-        return sk_sp<GrDrawOp>(
+        return std::unique_ptr<GrDrawOp>(
                 new AAHairlineOp(color, newCoverage, viewMatrix, path, devClipBounds));
     }
 
@@ -723,9 +723,6 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fColor);
         fUsesLocalCoords = optimizations.readsLocalCoords();
     }
@@ -840,11 +837,10 @@
             using namespace GrDefaultGeoProcFactory;
 
             Color color(this->color());
-            Coverage coverage(Coverage::kAttribute_Type);
             LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosition_Type :
                                     LocalCoords::kUnused_Type);
             localCoords.fMatrix = geometryProcessorLocalM;
-            lineGP = GrDefaultGeoProcFactory::Make(color, coverage, localCoords,
+            lineGP = GrDefaultGeoProcFactory::Make(color, Coverage::kAttribute_Type, localCoords,
                                                    *geometryProcessorViewM);
         }
 
@@ -956,9 +952,9 @@
                                       &devClipBounds);
     SkPath path;
     args.fShape->asPath(&path);
-    sk_sp<GrDrawOp> op = AAHairlineOp::Make(args.fPaint->getColor(), *args.fViewMatrix, path,
-                                            args.fShape->style(), devClipBounds);
-    GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fAAType);
+    std::unique_ptr<GrDrawOp> op = AAHairlineOp::Make(args.fPaint.getColor(), *args.fViewMatrix,
+                                                      path, args.fShape->style(), devClipBounds);
+    GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
     pipelineBuilder.setUserStencil(args.fUserStencilSettings);
     args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(op));
     return true;
diff --git a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
index b96e90a..caa086e 100644
--- a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
@@ -103,38 +103,32 @@
 
 static sk_sp<GrGeometryProcessor> create_fill_gp(bool tweakAlphaForCoverage,
                                                  const SkMatrix& viewMatrix,
-                                                 bool usesLocalCoords,
-                                                 bool coverageIgnored) {
+                                                 bool usesLocalCoords) {
     using namespace GrDefaultGeoProcFactory;
 
-    Color color(Color::kAttribute_Type);
     Coverage::Type coverageType;
-    // TODO remove coverage if coverage is ignored
-    /*if (coverageIgnored) {
-        coverageType = Coverage::kNone_Type;
-    } else*/ if (tweakAlphaForCoverage) {
+    if (tweakAlphaForCoverage) {
         coverageType = Coverage::kSolid_Type;
     } else {
         coverageType = Coverage::kAttribute_Type;
     }
-    Coverage coverage(coverageType);
-    LocalCoords localCoords(usesLocalCoords ? LocalCoords::kUsePosition_Type :
-                                              LocalCoords::kUnused_Type);
-    return MakeForDeviceSpace(color, coverage, localCoords, viewMatrix);
+    LocalCoords::Type localCoordsType =
+            usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
+    return MakeForDeviceSpace(Color::kAttribute_Type, coverageType, localCoordsType, viewMatrix);
 }
 
 class AAFlatteningConvexPathOp final : public GrMeshDrawOp {
 public:
     DEFINE_OP_CLASS_ID
-    static sk_sp<GrDrawOp> Make(GrColor color,
-                                const SkMatrix& viewMatrix,
-                                const SkPath& path,
-                                SkScalar strokeWidth,
-                                SkStrokeRec::Style style,
-                                SkPaint::Join join,
-                                SkScalar miterLimit) {
-        return sk_sp<GrDrawOp>(new AAFlatteningConvexPathOp(color, viewMatrix, path, strokeWidth,
-                                                            style, join, miterLimit));
+    static std::unique_ptr<GrDrawOp> Make(GrColor color,
+                                          const SkMatrix& viewMatrix,
+                                          const SkPath& path,
+                                          SkScalar strokeWidth,
+                                          SkStrokeRec::Style style,
+                                          SkPaint::Join join,
+                                          SkScalar miterLimit) {
+        return std::unique_ptr<GrDrawOp>(new AAFlatteningConvexPathOp(
+                color, viewMatrix, path, strokeWidth, style, join, miterLimit));
     }
 
     const char* name() const override { return "AAFlatteningConvexPathOp"; }
@@ -184,14 +178,8 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fPaths[0].fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fPaths[0].fColor);
-
-        fColor = fPaths[0].fColor;
         fUsesLocalCoords = optimizations.readsLocalCoords();
-        fCoverageIgnored = !optimizations.readsCoverage();
         fCanTweakAlphaForCoverage = optimizations.canTweakAlphaForCoverage();
     }
 
@@ -228,10 +216,8 @@
         bool canTweakAlphaForCoverage = this->canTweakAlphaForCoverage();
 
         // Setup GrGeometryProcessor
-        sk_sp<GrGeometryProcessor> gp(create_fill_gp(canTweakAlphaForCoverage,
-                                                     this->viewMatrix(),
-                                                     this->usesLocalCoords(),
-                                                     this->coverageIgnored()));
+        sk_sp<GrGeometryProcessor> gp(create_fill_gp(
+                canTweakAlphaForCoverage, this->viewMatrix(), this->usesLocalCoords()));
         if (!gp) {
             SkDebugf("Couldn't create a GrGeometryProcessor\n");
             return;
@@ -313,11 +299,9 @@
         return true;
     }
 
-    GrColor color() const { return fColor; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
     bool canTweakAlphaForCoverage() const { return fCanTweakAlphaForCoverage; }
     const SkMatrix& viewMatrix() const { return fPaths[0].fViewMatrix; }
-    bool coverageIgnored() const { return fCoverageIgnored; }
 
     struct PathData {
         GrColor fColor;
@@ -329,9 +313,7 @@
         SkScalar fMiterLimit;
     };
 
-    GrColor fColor;
     bool fUsesLocalCoords;
-    bool fCoverageIgnored;
     bool fCanTweakAlphaForCoverage;
     SkSTArray<1, PathData, true> fPaths;
 
@@ -353,11 +335,11 @@
     SkPaint::Join join = fill ? SkPaint::Join::kMiter_Join : stroke.getJoin();
     SkScalar miterLimit = stroke.getMiter();
 
-    sk_sp<GrDrawOp> op =
-            AAFlatteningConvexPathOp::Make(args.fPaint->getColor(), *args.fViewMatrix, path,
+    std::unique_ptr<GrDrawOp> op =
+            AAFlatteningConvexPathOp::Make(args.fPaint.getColor(), *args.fViewMatrix, path,
                                            strokeWidth, stroke.getStyle(), join, miterLimit);
 
-    GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fAAType);
+    GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
     pipelineBuilder.setUserStencil(args.fUserStencilSettings);
 
     args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(op));
diff --git a/src/gpu/ops/GrAAStrokeRectOp.cpp b/src/gpu/ops/GrAAStrokeRectOp.cpp
index 1c70059..8a5fec1 100644
--- a/src/gpu/ops/GrAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrAAStrokeRectOp.cpp
@@ -97,17 +97,15 @@
                                                         bool usesLocalCoords) {
     using namespace GrDefaultGeoProcFactory;
 
-    Color color(Color::kAttribute_Type);
     Coverage::Type coverageType;
     if (tweakAlphaForCoverage) {
         coverageType = Coverage::kSolid_Type;
     } else {
         coverageType = Coverage::kAttribute_Type;
     }
-    Coverage coverage(coverageType);
-    LocalCoords localCoords(usesLocalCoords ? LocalCoords::kUsePosition_Type
-                                            : LocalCoords::kUnused_Type);
-    return MakeForDeviceSpace(color, coverage, localCoords, viewMatrix);
+    LocalCoords::Type localCoordsType =
+            usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
+    return MakeForDeviceSpace(Color::kAttribute_Type, coverageType, localCoordsType, viewMatrix);
 }
 
 class AAStrokeRectOp final : public GrMeshDrawOp {
@@ -125,8 +123,8 @@
         fMiterStroke = true;
     }
 
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect,
-                                const SkStrokeRec& stroke) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix,
+                                          const SkRect& rect, const SkStrokeRec& stroke) {
         bool isMiter;
         if (!allowed_stroke(stroke, &isMiter)) {
             return nullptr;
@@ -140,7 +138,7 @@
         info.fColor = color;
         op->setBounds(info.fDevOutside, HasAABloat::kYes, IsZeroArea::kNo);
         op->fViewMatrix = viewMatrix;
-        return sk_sp<GrDrawOp>(op);
+        return std::unique_ptr<GrDrawOp>(op);
     }
 
     const char* name() const override { return "AAStrokeRect"; }
@@ -222,9 +220,6 @@
 };
 
 void AAStrokeRectOp::applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) {
-    if (!optimizations.readsColor()) {
-        fRects[0].fColor = GrColor_ILLEGAL;
-    }
     optimizations.getOverrideColorIfSet(&fRects[0].fColor);
 
     fUsesLocalCoords = optimizations.readsLocalCoords();
@@ -565,17 +560,17 @@
 
 namespace GrAAStrokeRectOp {
 
-sk_sp<GrDrawOp> MakeFillBetweenRects(GrColor color,
-                                     const SkMatrix& viewMatrix,
-                                     const SkRect& devOutside,
-                                     const SkRect& devInside) {
-    return sk_sp<GrDrawOp>(new AAStrokeRectOp(color, viewMatrix, devOutside, devInside));
+std::unique_ptr<GrDrawOp> MakeFillBetweenRects(GrColor color,
+                                               const SkMatrix& viewMatrix,
+                                               const SkRect& devOutside,
+                                               const SkRect& devInside) {
+    return std::unique_ptr<GrDrawOp>(new AAStrokeRectOp(color, viewMatrix, devOutside, devInside));
 }
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect,
-                     const SkStrokeRec& stroke) {
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkStrokeRec& stroke) {
     return AAStrokeRectOp::Make(color, viewMatrix, rect, stroke);
 }
 }
diff --git a/src/gpu/ops/GrAAStrokeRectOp.h b/src/gpu/ops/GrAAStrokeRectOp.h
index fe8f55c..d9f830e 100644
--- a/src/gpu/ops/GrAAStrokeRectOp.h
+++ b/src/gpu/ops/GrAAStrokeRectOp.h
@@ -18,15 +18,15 @@
 
 namespace GrAAStrokeRectOp {
 
-sk_sp<GrDrawOp> MakeFillBetweenRects(GrColor color,
-                                     const SkMatrix& viewMatrix,
-                                     const SkRect& devOutside,
-                                     const SkRect& devInside);
+std::unique_ptr<GrDrawOp> MakeFillBetweenRects(GrColor color,
+                                               const SkMatrix& viewMatrix,
+                                               const SkRect& devOutside,
+                                               const SkRect& devInside);
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect,
-                     const SkStrokeRec& stroke);
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkStrokeRec& stroke);
 }
 
 #endif
diff --git a/src/gpu/ops/GrAnalyticRectOp.cpp b/src/gpu/ops/GrAnalyticRectOp.cpp
index 01e9e33..52934ff 100644
--- a/src/gpu/ops/GrAnalyticRectOp.cpp
+++ b/src/gpu/ops/GrAnalyticRectOp.cpp
@@ -376,12 +376,13 @@
     typedef GrMeshDrawOp INHERITED;
 };
 
-sk_sp<GrDrawOp> GrAnalyticRectOp::Make(GrColor color,
-                                       const SkMatrix& viewMatrix,
-                                       const SkRect& rect,
-                                       const SkRect& croppedRect,
-                                       const SkRect& bounds) {
-    return sk_sp<GrDrawOp>(new AnalyticRectOp(color, viewMatrix, rect, croppedRect, bounds));
+std::unique_ptr<GrDrawOp> GrAnalyticRectOp::Make(GrColor color,
+                                                 const SkMatrix& viewMatrix,
+                                                 const SkRect& rect,
+                                                 const SkRect& croppedRect,
+                                                 const SkRect& bounds) {
+    return std::unique_ptr<GrDrawOp>(
+            new AnalyticRectOp(color, viewMatrix, rect, croppedRect, bounds));
 }
 
 #ifdef GR_TEST_UTILS
@@ -392,7 +393,8 @@
     SkRect rect = GrTest::TestSquare(random);
     SkRect croppedRect = GrTest::TestSquare(random);
     SkRect bounds = GrTest::TestSquare(random);
-    return sk_sp<GrDrawOp>(new AnalyticRectOp(color, viewMatrix, rect, croppedRect, bounds));
+    return std::unique_ptr<GrDrawOp>(
+            new AnalyticRectOp(color, viewMatrix, rect, croppedRect, bounds));
 }
 
 #endif
diff --git a/src/gpu/ops/GrAnalyticRectOp.h b/src/gpu/ops/GrAnalyticRectOp.h
index 77d17ed..1266879 100644
--- a/src/gpu/ops/GrAnalyticRectOp.h
+++ b/src/gpu/ops/GrAnalyticRectOp.h
@@ -27,11 +27,11 @@
  */
 class GrAnalyticRectOp {
 public:
-    static sk_sp<GrDrawOp> Make(GrColor color,
-                                const SkMatrix& viewMatrix,
-                                const SkRect& rect,
-                                const SkRect& croppedRect,
-                                const SkRect& bounds);
+    static std::unique_ptr<GrDrawOp> Make(GrColor color,
+                                          const SkMatrix& viewMatrix,
+                                          const SkRect& rect,
+                                          const SkRect& croppedRect,
+                                          const SkRect& bounds);
 };
 
 #endif  // GrAnalyticRectOp_DEFINED
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index c4c9170..dd17ad6 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -67,15 +67,10 @@
 }
 
 void GrAtlasTextOp::applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) {
-    if (!optimizations.readsColor()) {
-        fGeoData[0].fColor = GrColor_ILLEGAL;
-    }
     optimizations.getOverrideColorIfSet(&fGeoData[0].fColor);
 
-    fColorIgnored = !optimizations.readsColor();
     fColor = fGeoData[0].fColor;
     fUsesLocalCoords = optimizations.readsLocalCoords();
-    fCoverageIgnored = !optimizations.readsCoverage();
 }
 
 void GrAtlasTextOp::onPrepareDraws(Target* target) const {
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 961c4fb..2a8c30b 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -17,6 +17,12 @@
 public:
     DEFINE_OP_CLASS_ID
 
+    ~GrAtlasTextOp() override {
+        for (int i = 0; i < fGeoCount; i++) {
+            fGeoData[i].fBlob->unref();
+        }
+    }
+
     static const int kVerticesPerGlyph = GrAtlasTextBlob::kVerticesPerGlyph;
     static const int kIndicesPerGlyph = 6;
 
@@ -31,9 +37,9 @@
         GrColor fColor;
     };
 
-    static sk_sp<GrAtlasTextOp> MakeBitmap(GrMaskFormat maskFormat, int glyphCount,
-                                           GrAtlasGlyphCache* fontCache) {
-        sk_sp<GrAtlasTextOp> op(new GrAtlasTextOp);
+    static std::unique_ptr<GrAtlasTextOp> MakeBitmap(GrMaskFormat maskFormat, int glyphCount,
+                                                     GrAtlasGlyphCache* fontCache) {
+        std::unique_ptr<GrAtlasTextOp> op(new GrAtlasTextOp);
 
         op->fFontCache = fontCache;
         switch (maskFormat) {
@@ -55,11 +61,11 @@
         return op;
     }
 
-    static sk_sp<GrAtlasTextOp> MakeDistanceField(
+    static std::unique_ptr<GrAtlasTextOp> MakeDistanceField(
             int glyphCount, GrAtlasGlyphCache* fontCache,
             const GrDistanceFieldAdjustTable* distanceAdjustTable,
             bool useGammaCorrectDistanceTable, SkColor filteredColor, bool isLCD, bool useBGR) {
-        sk_sp<GrAtlasTextOp> op(new GrAtlasTextOp);
+        std::unique_ptr<GrAtlasTextOp> op(new GrAtlasTextOp);
 
         op->fFontCache = fontCache;
         op->fMaskType = isLCD ? kLCDDistanceField_MaskType : kGrayscaleDistanceField_MaskType;
@@ -108,12 +114,6 @@
 
     GrAtlasTextOp() : INHERITED(ClassID()) {}  // initialized in factory functions.
 
-    ~GrAtlasTextOp() {
-        for (int i = 0; i < fGeoCount; i++) {
-            fGeoData[i].fBlob->unref();
-        }
-    }
-
     GrMaskFormat maskFormat() const {
         switch (fMaskType) {
             case kLCDCoverageMask_MaskType:
@@ -153,8 +153,6 @@
 
     GrColor fColor;
     bool fUsesLocalCoords;
-    bool fColorIgnored;
-    bool fCoverageIgnored;
     int fNumGlyphs;
 
     // The minimum number of Geometry we will try to allocate.
diff --git a/src/gpu/ops/GrClearOp.h b/src/gpu/ops/GrClearOp.h
index 1a4b33a..b40b615 100644
--- a/src/gpu/ops/GrClearOp.h
+++ b/src/gpu/ops/GrClearOp.h
@@ -19,26 +19,22 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrClearOp> Make(const GrFixedClip& clip, GrColor color, GrRenderTarget* rt) {
-        sk_sp<GrClearOp> op(new GrClearOp(clip, color, rt));
+    static std::unique_ptr<GrClearOp> Make(const GrFixedClip& clip, GrColor color,
+                                           GrRenderTarget* rt) {
+        std::unique_ptr<GrClearOp> op(new GrClearOp(clip, color, rt));
         if (!op->fRenderTarget) {
             return nullptr; // The clip did not contain any pixels within the render target.
         }
         return op;
     }
 
-    static sk_sp<GrClearOp> Make(const SkIRect& rect, GrColor color, GrRenderTarget* rt,
-                                    bool fullScreen) {
-        return sk_sp<GrClearOp>(new GrClearOp(rect, color, rt, fullScreen));
+    static std::unique_ptr<GrClearOp> Make(const SkIRect& rect, GrColor color, GrRenderTarget* rt,
+                                           bool fullScreen) {
+        return std::unique_ptr<GrClearOp>(new GrClearOp(rect, color, rt, fullScreen));
     }
 
     const char* name() const override { return "Clear"; }
 
-    // TODO: this needs to be updated to return GrSurfaceProxy::UniqueID
-    GrGpuResource::UniqueID renderTargetUniqueID() const override {
-        return fRenderTarget.get()->uniqueID();
-    }
-
     SkString dumpInfo() const override {
         SkString string("Scissor [");
         if (fClip.scissorEnabled()) {
diff --git a/src/gpu/ops/GrClearStencilClipOp.h b/src/gpu/ops/GrClearStencilClipOp.h
index a5d6a03..afbd72d 100644
--- a/src/gpu/ops/GrClearStencilClipOp.h
+++ b/src/gpu/ops/GrClearStencilClipOp.h
@@ -19,17 +19,13 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrOp> Make(const GrFixedClip& clip, bool insideStencilMask, GrRenderTarget* rt) {
-        return sk_sp<GrOp>(new GrClearStencilClipOp(clip, insideStencilMask, rt));
+    static std::unique_ptr<GrOp> Make(const GrFixedClip& clip, bool insideStencilMask,
+                                      GrRenderTarget* rt) {
+        return std::unique_ptr<GrOp>(new GrClearStencilClipOp(clip, insideStencilMask, rt));
     }
 
     const char* name() const override { return "ClearStencilClip"; }
 
-    // TODO: this needs to be updated to return GrSurfaceProxy::UniqueID
-    GrGpuResource::UniqueID renderTargetUniqueID() const override {
-        return fRenderTarget.get()->uniqueID();
-    }
-
     SkString dumpInfo() const override {
         SkString string("Scissor [");
         if (fClip.scissorEnabled()) {
diff --git a/src/gpu/ops/GrCopySurfaceOp.cpp b/src/gpu/ops/GrCopySurfaceOp.cpp
index 3283517..87c490b 100644
--- a/src/gpu/ops/GrCopySurfaceOp.cpp
+++ b/src/gpu/ops/GrCopySurfaceOp.cpp
@@ -58,8 +58,8 @@
     return !clippedSrcRect->isEmpty();
 }
 
-sk_sp<GrOp> GrCopySurfaceOp::Make(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
-                                  const SkIPoint& dstPoint) {
+std::unique_ptr<GrOp> GrCopySurfaceOp::Make(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
+                                            const SkIPoint& dstPoint) {
     SkASSERT(dst);
     SkASSERT(src);
     if (GrPixelConfigIsSint(dst->config()) != GrPixelConfigIsSint(src->config())) {
@@ -74,5 +74,5 @@
     if (!ClipSrcRectAndDstPoint(dst, src, srcRect, dstPoint, &clippedSrcRect, &clippedDstPoint)) {
         return nullptr;
     }
-    return sk_sp<GrOp>(new GrCopySurfaceOp(dst, src, clippedSrcRect, clippedDstPoint));
+    return std::unique_ptr<GrOp>(new GrCopySurfaceOp(dst, src, clippedSrcRect, clippedDstPoint));
 }
diff --git a/src/gpu/ops/GrCopySurfaceOp.h b/src/gpu/ops/GrCopySurfaceOp.h
index 789f7a2..e74b7e5 100644
--- a/src/gpu/ops/GrCopySurfaceOp.h
+++ b/src/gpu/ops/GrCopySurfaceOp.h
@@ -27,19 +27,11 @@
                                        SkIRect* clippedSrcRect,
                                        SkIPoint* clippedDstPoint);
 
-    static sk_sp<GrOp> Make(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
-                            const SkIPoint& dstPoint);
+    static std::unique_ptr<GrOp> Make(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
+                                      const SkIPoint& dstPoint);
 
     const char* name() const override { return "CopySurface"; }
 
-    // TODO: this needs to be updated to return GrSurfaceProxy::UniqueID
-    GrGpuResource::UniqueID renderTargetUniqueID() const override {
-        // Copy surface doesn't work through a GrGpuCommandBuffer. By returning an invalid RT ID we
-        // force the caller to end the previous command buffer and execute this copy before
-        // beginning a new one.
-        return GrGpuResource::UniqueID::InvalidID();
-    }
-
     SkString dumpInfo() const override {
         SkString string;
         string.printf(
diff --git a/src/gpu/ops/GrDashLinePathRenderer.cpp b/src/gpu/ops/GrDashLinePathRenderer.cpp
index 10e4342..1c43cb5 100644
--- a/src/gpu/ops/GrDashLinePathRenderer.cpp
+++ b/src/gpu/ops/GrDashLinePathRenderer.cpp
@@ -45,13 +45,13 @@
     }
     SkPoint pts[2];
     SkAssertResult(args.fShape->asLine(pts, nullptr));
-    sk_sp<GrDrawOp> op = GrDashOp::MakeDashLineOp(
-            args.fPaint->getColor(), *args.fViewMatrix, pts, aaMode, args.fShape->style());
+    std::unique_ptr<GrDrawOp> op = GrDashOp::MakeDashLineOp(
+            args.fPaint.getColor(), *args.fViewMatrix, pts, aaMode, args.fShape->style());
     if (!op) {
         return false;
     }
 
-    GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fAAType);
+    GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
     pipelineBuilder.setUserStencil(args.fUserStencilSettings);
 
     args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(op));
diff --git a/src/gpu/ops/GrDashOp.cpp b/src/gpu/ops/GrDashOp.cpp
index 8a9dfe8..d4be853 100644
--- a/src/gpu/ops/GrDashOp.cpp
+++ b/src/gpu/ops/GrDashOp.cpp
@@ -252,9 +252,9 @@
         SkScalar fPerpendicularScale;
     };
 
-    static sk_sp<GrDrawOp> Make(const LineData& geometry, GrColor color, SkPaint::Cap cap,
-                                AAMode aaMode, bool fullDash) {
-        return sk_sp<GrDrawOp>(new DashOp(geometry, color, cap, aaMode, fullDash));
+    static std::unique_ptr<GrDrawOp> Make(const LineData& geometry, GrColor color, SkPaint::Cap cap,
+                                          AAMode aaMode, bool fullDash) {
+        return std::unique_ptr<GrDrawOp>(new DashOp(geometry, color, cap, aaMode, fullDash));
     }
 
     const char* name() const override { return "DashOp"; }
@@ -303,13 +303,9 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fColor);
 
         fUsesLocalCoords = optimizations.readsLocalCoords();
-        fCoverageIgnored = !optimizations.readsCoverage();
     }
 
     struct DashDraw {
@@ -346,11 +342,11 @@
             // Set up the vertex data for the line and start/end dashes
             using namespace GrDefaultGeoProcFactory;
             Color color(this->color());
-            Coverage coverage(this->coverageIgnored() ? Coverage::kNone_Type :
-                                                        Coverage::kSolid_Type);
-            LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosition_Type :
-                                                              LocalCoords::kUnused_Type);
-            gp = MakeForDeviceSpace(color, coverage, localCoords, this->viewMatrix());
+            LocalCoords::Type localCoordsType = this->usesLocalCoords()
+                                                        ? LocalCoords::kUsePosition_Type
+                                                        : LocalCoords::kUnused_Type;
+            gp = MakeForDeviceSpace(color, Coverage::kSolid_Type, localCoordsType,
+                                    this->viewMatrix());
         }
 
         if (!gp) {
@@ -672,14 +668,12 @@
     AAMode aaMode() const { return fAAMode; }
     bool fullDash() const { return fFullDash; }
     SkPaint::Cap cap() const { return fCap; }
-    bool coverageIgnored() const { return fCoverageIgnored; }
 
     static const int kVertsPerDash = 4;
     static const int kIndicesPerDash = 6;
 
     GrColor fColor;
     bool fUsesLocalCoords;
-    bool fCoverageIgnored;
     SkPaint::Cap fCap;
     AAMode fAAMode;
     bool fFullDash;
@@ -688,11 +682,11 @@
     typedef GrMeshDrawOp INHERITED;
 };
 
-sk_sp<GrDrawOp> GrDashOp::MakeDashLineOp(GrColor color,
-                                         const SkMatrix& viewMatrix,
-                                         const SkPoint pts[2],
-                                         AAMode aaMode,
-                                         const GrStyle& style) {
+std::unique_ptr<GrDrawOp> GrDashOp::MakeDashLineOp(GrColor color,
+                                                   const SkMatrix& viewMatrix,
+                                                   const SkPoint pts[2],
+                                                   AAMode aaMode,
+                                                   const GrStyle& style) {
     SkASSERT(GrDashOp::CanDrawDashLine(pts, style, viewMatrix));
     const SkScalar* intervals = style.dashIntervals();
     SkScalar phase = style.dashPhase();
@@ -775,8 +769,6 @@
 
     GrColor color() const { return fColor; }
 
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
-
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
 
     bool usesLocalCoords() const { return fUsesLocalCoords; }
@@ -854,9 +846,7 @@
 
     GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
-    if (!dce.colorIgnored()) {
-        this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
-    }
+    this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
     // Setup position
     this->setupPosition(vertBuilder, gpArgs, dce.inPosition()->fName);
@@ -907,8 +897,7 @@
     const DashingCircleEffect& dce = gp.cast<DashingCircleEffect>();
     uint32_t key = 0;
     key |= dce.usesLocalCoords() && dce.localMatrix().hasPerspective() ? 0x1 : 0x0;
-    key |= dce.colorIgnored() ? 0x2 : 0x0;
-    key |= static_cast<uint32_t>(dce.aaMode()) << 8;
+    key |= static_cast<uint32_t>(dce.aaMode()) << 1;
     b->add32(key);
 }
 
@@ -988,9 +977,7 @@
 
     GrColor color() const { return fColor; }
 
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
-
-    const SkMatrix& localMatrix() const { return fLocalMatrix; }
+     const SkMatrix& localMatrix() const { return fLocalMatrix; }
 
     bool usesLocalCoords() const { return fUsesLocalCoords; }
 
@@ -1036,9 +1023,7 @@
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
-GLDashingLineEffect::GLDashingLineEffect() {
-    fColor = GrColor_ILLEGAL;
-}
+GLDashingLineEffect::GLDashingLineEffect() : fColor(GrColor_ILLEGAL) {}
 
 void GLDashingLineEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
     const DashingLineEffect& de = args.fGP.cast<DashingLineEffect>();
@@ -1063,9 +1048,7 @@
 
     GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
-    if (!de.colorIgnored()) {
-        this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
-    }
+    this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
     // Setup position
     this->setupPosition(vertBuilder, gpArgs, de.inPosition()->fName);
@@ -1134,7 +1117,6 @@
     const DashingLineEffect& de = gp.cast<DashingLineEffect>();
     uint32_t key = 0;
     key |= de.usesLocalCoords() && de.localMatrix().hasPerspective() ? 0x1 : 0x0;
-    key |= de.colorIgnored() ? 0x2 : 0x0;
     key |= static_cast<int>(de.aaMode()) << 8;
     b->add32(key);
 }
diff --git a/src/gpu/ops/GrDashOp.h b/src/gpu/ops/GrDashOp.h
index ff04f91..2f4a30f 100644
--- a/src/gpu/ops/GrDashOp.h
+++ b/src/gpu/ops/GrDashOp.h
@@ -23,8 +23,8 @@
 };
 static const int kAAModeCnt = static_cast<int>(AAMode::kCoverageWithMSAA) + 1;
 
-sk_sp<GrDrawOp> MakeDashLineOp(GrColor, const SkMatrix& viewMatrix, const SkPoint pts[2], AAMode,
-                               const GrStyle& style);
+std::unique_ptr<GrDrawOp> MakeDashLineOp(GrColor, const SkMatrix& viewMatrix, const SkPoint pts[2],
+                                         AAMode, const GrStyle& style);
 bool CanDrawDashLine(const SkPoint pts[2], const GrStyle& style, const SkMatrix& viewMatrix);
 }
 
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
index 47c7ada..270dc09 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.cpp
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -98,11 +98,11 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkPath& path, SkScalar tolerance,
-                                uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline,
-                                const SkRect& devBounds) {
-        return sk_sp<GrDrawOp>(new DefaultPathOp(color, path, tolerance, coverage, viewMatrix,
-                                                 isHairline, devBounds));
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkPath& path, SkScalar tolerance,
+                                          uint8_t coverage, const SkMatrix& viewMatrix,
+                                          bool isHairline, const SkRect& devBounds) {
+        return std::unique_ptr<GrDrawOp>(new DefaultPathOp(color, path, tolerance, coverage,
+                                                           viewMatrix, isHairline, devBounds));
     }
 
     const char* name() const override { return "DefaultPathOp"; }
@@ -138,12 +138,8 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fColor);
         fUsesLocalCoords = optimizations.readsLocalCoords();
-        fCoverageIgnored = !optimizations.readsCoverage();
     }
 
     void onPrepareDraws(Target* target) const override {
@@ -152,9 +148,6 @@
             using namespace GrDefaultGeoProcFactory;
             Color color(this->color());
             Coverage coverage(this->coverage());
-            if (this->coverageIgnored()) {
-                coverage.fType = Coverage::kNone_Type;
-            }
             LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosition_Type :
                                                               LocalCoords::kUnused_Type);
             gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix());
@@ -395,7 +388,6 @@
     bool usesLocalCoords() const { return fUsesLocalCoords; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     bool isHairline() const { return fIsHairline; }
-    bool coverageIgnored() const { return fCoverageIgnored; }
 
     struct PathData {
         SkPath fPath;
@@ -406,7 +398,6 @@
     uint8_t fCoverage;
     SkMatrix fViewMatrix;
     bool fUsesLocalCoords;
-    bool fCoverageIgnored;
     bool fIsHairline;
     SkSTArray<1, PathData, true> fPaths;
 
@@ -414,7 +405,7 @@
 };
 
 bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext,
-                                             const GrPaint& paint,
+                                             GrPaint&& paint,
                                              GrAAType aaType,
                                              const GrUserStencilSettings& userStencilSettings,
                                              const GrClip& clip,
@@ -555,19 +546,20 @@
             }
             const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
                                                                                viewMatrix;
-            sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(paint.getColor(), viewM, bounds,
-                                                              nullptr, &localMatrix));
+            std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
+                    paint.getColor(), viewM, bounds, nullptr, &localMatrix));
 
             SkASSERT(GrDrawFace::kBoth == drawFace[p]);
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
             pipelineBuilder.setDrawFace(drawFace[p]);
             pipelineBuilder.setUserStencil(passes[p]);
             renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
         } else {
-            sk_sp<GrDrawOp> op =
+            std::unique_ptr<GrDrawOp> op =
                     DefaultPathOp::Make(paint.getColor(), path, srcSpaceTol, newCoverage,
+
                                         viewMatrix, isHairline, devBounds);
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
+            GrPipelineBuilder pipelineBuilder(GrPaint::MoveOrNew(paint, lastPassIsBounds), aaType);
             pipelineBuilder.setDrawFace(drawFace[p]);
             pipelineBuilder.setUserStencil(passes[p]);
             if (passCount > 1) {
@@ -590,7 +582,7 @@
     GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
                               "GrDefaultPathRenderer::onDrawPath");
     return this->internalDrawPath(args.fRenderTargetContext,
-                                  *args.fPaint,
+                                  std::move(args.fPaint),
                                   args.fAAType,
                                   *args.fUserStencilSettings,
                                   *args.fClip,
@@ -605,9 +597,9 @@
     SkASSERT(!args.fShape->inverseFilled());
 
     GrPaint paint;
-    paint.setXPFactory(GrDisableColorXPFactory::Make());
+    paint.setXPFactory(GrDisableColorXPFactory::Get());
 
-    this->internalDrawPath(args.fRenderTargetContext, paint, args.fAAType,
+    this->internalDrawPath(args.fRenderTargetContext, std::move(paint), args.fAAType,
                            GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix,
                            *args.fShape, true);
 }
diff --git a/src/gpu/ops/GrDefaultPathRenderer.h b/src/gpu/ops/GrDefaultPathRenderer.h
index 8e19247..0a84eb9 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.h
+++ b/src/gpu/ops/GrDefaultPathRenderer.h
@@ -31,7 +31,7 @@
     void onStencilPath(const StencilPathArgs&) override;
 
     bool internalDrawPath(GrRenderTargetContext*,
-                          const GrPaint&,
+                          GrPaint&&,
                           GrAAType,
                           const GrUserStencilSettings&,
                           const GrClip&,
diff --git a/src/gpu/ops/GrDiscardOp.h b/src/gpu/ops/GrDiscardOp.h
index e7225af..f9be33d 100644
--- a/src/gpu/ops/GrDiscardOp.h
+++ b/src/gpu/ops/GrDiscardOp.h
@@ -16,15 +16,12 @@
 class GrDiscardOp final : public GrOp {
 public:
     DEFINE_OP_CLASS_ID
-    static sk_sp<GrOp> Make(GrRenderTarget* rt) { return sk_sp<GrOp>(new GrDiscardOp(rt)); }
+    static std::unique_ptr<GrOp> Make(GrRenderTarget* rt) {
+        return std::unique_ptr<GrOp>(new GrDiscardOp(rt));
+    }
 
     const char* name() const override { return "Discard"; }
 
-    // TODO: this needs to be updated to return GrSurfaceProxy::UniqueID
-    GrGpuResource::UniqueID renderTargetUniqueID() const override {
-        return fRenderTarget.get()->uniqueID();
-    }
-
     SkString dumpInfo() const override {
         SkString string;
         string.printf("RT: %d", fRenderTarget.get()->uniqueID().asUInt());
@@ -39,7 +36,7 @@
     }
 
     bool onCombineIfPossible(GrOp* that, const GrCaps& caps) override {
-        return this->renderTargetUniqueID() == that->renderTargetUniqueID();
+        return fRenderTarget.get() == that->cast<GrDiscardOp>()->fRenderTarget.get();
     }
 
     void onPrepare(GrOpFlushState*) override {}
diff --git a/src/gpu/ops/GrDrawAtlasOp.cpp b/src/gpu/ops/GrDrawAtlasOp.cpp
index eac8f96..c9dffe6 100644
--- a/src/gpu/ops/GrDrawAtlasOp.cpp
+++ b/src/gpu/ops/GrDrawAtlasOp.cpp
@@ -14,9 +14,6 @@
 
 void GrDrawAtlasOp::applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) {
     SkASSERT(fGeoData.count() == 1);
-    if (!optimizations.readsColor()) {
-        fGeoData[0].fColor = GrColor_ILLEGAL;
-    }
     if (optimizations.getOverrideColorIfSet(&fGeoData[0].fColor) && fHasColors) {
         size_t vertexStride =
                 sizeof(SkPoint) + sizeof(SkPoint) + (this->hasColors() ? sizeof(GrColor) : 0);
@@ -27,32 +24,27 @@
         }
     }
 
-    fColorIgnored = !optimizations.readsColor();
     fColor = fGeoData[0].fColor;
     // We'd like to assert this, but we can't because of GLPrograms test
     // SkASSERT(init.readsLocalCoords());
-    fCoverageIgnored = !optimizations.readsCoverage();
 }
 
-static sk_sp<GrGeometryProcessor> set_vertex_attributes(bool hasColors,
-                                                        GrColor color,
-                                                        const SkMatrix& viewMatrix,
-                                                        bool coverageIgnored) {
+static sk_sp<GrGeometryProcessor> make_gp(bool hasColors,
+                                          GrColor color,
+                                          const SkMatrix& viewMatrix) {
     using namespace GrDefaultGeoProcFactory;
     Color gpColor(color);
     if (hasColors) {
         gpColor.fType = Color::kAttribute_Type;
     }
 
-    Coverage coverage(coverageIgnored ? Coverage::kNone_Type : Coverage::kSolid_Type);
-    LocalCoords localCoords(LocalCoords::kHasExplicit_Type);
-    return GrDefaultGeoProcFactory::Make(gpColor, coverage, localCoords, viewMatrix);
+    return GrDefaultGeoProcFactory::Make(gpColor, Coverage::kSolid_Type,
+                                         LocalCoords::kHasExplicit_Type, viewMatrix);
 }
 
 void GrDrawAtlasOp::onPrepareDraws(Target* target) const {
     // Setup geometry processor
-    sk_sp<GrGeometryProcessor> gp(set_vertex_attributes(
-            this->hasColors(), this->color(), this->viewMatrix(), this->coverageIgnored()));
+    sk_sp<GrGeometryProcessor> gp(make_gp(this->hasColors(), this->color(), this->viewMatrix()));
 
     int instanceCount = fGeoData.count();
     size_t vertexStride = gp->getVertexStride();
@@ -180,9 +172,6 @@
         return false;
     }
 
-    if (this->color() != that->color()) {
-        fColor = GrColor_ILLEGAL;
-    }
     fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
     fQuadCount += that->quadCount();
 
diff --git a/src/gpu/ops/GrDrawAtlasOp.h b/src/gpu/ops/GrDrawAtlasOp.h
index ec21162..8184ecb 100644
--- a/src/gpu/ops/GrDrawAtlasOp.h
+++ b/src/gpu/ops/GrDrawAtlasOp.h
@@ -16,10 +16,10 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, int spriteCount,
-                                const SkRSXform* xforms, const SkRect* rects,
-                                const SkColor* colors) {
-        return sk_sp<GrDrawOp>(
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix,
+                                          int spriteCount, const SkRSXform* xforms,
+                                          const SkRect* rects, const SkColor* colors) {
+        return std::unique_ptr<GrDrawOp>(
                 new GrDrawAtlasOp(color, viewMatrix, spriteCount, xforms, rects, colors));
     }
 
@@ -53,11 +53,9 @@
     void applyPipelineOptimizations(const GrPipelineOptimizations&) override;
 
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return fColorIgnored; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     bool hasColors() const { return fHasColors; }
     int quadCount() const { return fQuadCount; }
-    bool coverageIgnored() const { return fCoverageIgnored; }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps&) override;
 
@@ -71,8 +69,6 @@
     SkMatrix fViewMatrix;
     GrColor fColor;
     int fQuadCount;
-    bool fColorIgnored;
-    bool fCoverageIgnored;
     bool fHasColors;
 
     typedef GrMeshDrawOp INHERITED;
diff --git a/src/gpu/ops/GrDrawOp.h b/src/gpu/ops/GrDrawOp.h
index ad8a545..9e949b1 100644
--- a/src/gpu/ops/GrDrawOp.h
+++ b/src/gpu/ops/GrDrawOp.h
@@ -67,12 +67,6 @@
     // TODO no GrPrimitiveProcessors yet read fragment position
     bool willReadFragmentPosition() const { return false; }
 
-    // TODO: this needs to be updated to return GrSurfaceProxy::UniqueID
-    // This is a bit more exciting than the other call sites since it uses the pipeline
-    GrGpuResource::UniqueID renderTargetUniqueID() const final {
-        return this->pipeline()->getRenderTarget()->uniqueID();
-    }
-
 protected:
     static SkString DumpPipelineInfo(const GrPipeline& pipeline) {
         SkString string;
diff --git a/src/gpu/ops/GrDrawPathOp.cpp b/src/gpu/ops/GrDrawPathOp.cpp
index e60311b..a32d708 100644
--- a/src/gpu/ops/GrDrawPathOp.cpp
+++ b/src/gpu/ops/GrDrawPathOp.cpp
@@ -31,7 +31,7 @@
     GrProgramDesc desc;
 
     sk_sp<GrPathProcessor> pathProc(
-            GrPathProcessor::Create(this->color(), this->optimizations(), this->viewMatrix()));
+            GrPathProcessor::Create(this->color(), this->viewMatrix()));
     state->gpu()->pathRendering()->drawPath(*this->pipeline(), *pathProc,
                                             this->stencilPassSettings(), fPath.get());
 }
@@ -100,10 +100,10 @@
     // combined. (Glyphs in the same font tend to wind the same direction so it works out OK.)
     if (GrPathRendering::kWinding_FillType != this->fillType() ||
         GrPathRendering::kWinding_FillType != that->fillType() ||
-        this->optimizations().willColorBlendWithDst()) {
+        this->blendsWithDst()) {
         return false;
     }
-    SkASSERT(!that->optimizations().willColorBlendWithDst());
+    SkASSERT(!that->blendsWithDst());
     fTotalPathCount += that->fTotalPathCount;
     while (Draw* head = that->fDraws.head()) {
         Draw* draw = fDraws.addToTail();
@@ -128,7 +128,7 @@
     localMatrix.preTranslate(head.fX, head.fY);
 
     sk_sp<GrPathProcessor> pathProc(
-            GrPathProcessor::Create(this->color(), this->optimizations(), drawMatrix, localMatrix));
+            GrPathProcessor::Create(this->color(), drawMatrix, localMatrix));
 
     if (fDraws.count() == 1) {
         const InstanceData& instances = *head.fInstanceData;
diff --git a/src/gpu/ops/GrDrawPathOp.h b/src/gpu/ops/GrDrawPathOp.h
index 95cbc7e..a18a351 100644
--- a/src/gpu/ops/GrDrawPathOp.h
+++ b/src/gpu/ops/GrDrawPathOp.h
@@ -30,10 +30,10 @@
     }
 
 protected:
-    const GrPipelineOptimizations& optimizations() const { return fOptimizations; }
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     GrColor color() const { return fColor; }
     GrPathRendering::FillType fillType() const { return fFillType; }
+    bool blendsWithDst() const { return fBlendsWithDst; }
 
 private:
     void getPipelineAnalysisInput(GrPipelineAnalysisDrawOpInput* input) const override {
@@ -43,7 +43,7 @@
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
         optimizations.getOverrideColorIfSet(&fColor);
-        fOptimizations = optimizations;
+        fBlendsWithDst = optimizations.willColorBlendWithDst();
     }
 
     void onPrepare(GrOpFlushState*) override;  // Initializes fStencilPassSettings.
@@ -52,7 +52,7 @@
     GrColor fColor;
     GrPathRendering::FillType fFillType;
     GrStencilSettings fStencilPassSettings;
-    GrPipelineOptimizations fOptimizations;
+    bool fBlendsWithDst;
 
     typedef GrDrawOp INHERITED;
 };
@@ -61,8 +61,9 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrDrawOp> Make(const SkMatrix& viewMatrix, GrColor color, const GrPath* path) {
-        return sk_sp<GrDrawOp>(new GrDrawPathOp(viewMatrix, color, path));
+    static std::unique_ptr<GrDrawOp> Make(const SkMatrix& viewMatrix, GrColor color,
+                                          const GrPath* path) {
+        return std::unique_ptr<GrDrawOp>(new GrDrawPathOp(viewMatrix, color, path));
     }
 
     const char* name() const override { return "DrawPath"; }
@@ -91,7 +92,7 @@
 
     DEFINE_OP_CLASS_ID
 
-    struct InstanceData : public SkNoncopyable {
+    struct InstanceData : private ::SkNoncopyable {
     public:
         static InstanceData* Alloc(TransformType transformType, int reserveCnt) {
             int transformSize = GrPathRendering::PathTransformSize(transformType);
@@ -150,11 +151,12 @@
         SkDEBUGCODE(int fReserveCnt;)
     };
 
-    static sk_sp<GrDrawOp> Make(const SkMatrix& viewMatrix, SkScalar scale, SkScalar x, SkScalar y,
-                                GrColor color, GrPathRendering::FillType fill, GrPathRange* range,
-                                const InstanceData* instanceData, const SkRect& bounds) {
-        return sk_sp<GrDrawOp>(new GrDrawPathRangeOp(viewMatrix, scale, x, y, color, fill, range,
-                                                     instanceData, bounds));
+    static std::unique_ptr<GrDrawOp> Make(const SkMatrix& viewMatrix, SkScalar scale, SkScalar x,
+                                          SkScalar y, GrColor color, GrPathRendering::FillType fill,
+                                          GrPathRange* range, const InstanceData* instanceData,
+                                          const SkRect& bounds) {
+        return std::unique_ptr<GrDrawOp>(new GrDrawPathRangeOp(viewMatrix, scale, x, y, color, fill,
+                                                               range, instanceData, bounds));
     }
 
     const char* name() const override { return "DrawPathRange"; }
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index db505f9..ed2c072 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -14,21 +14,19 @@
 static sk_sp<GrGeometryProcessor> set_vertex_attributes(bool hasLocalCoords,
                                                         int* colorOffset,
                                                         int* texOffset,
-                                                        const SkMatrix& viewMatrix,
-                                                        bool coverageIgnored) {
+                                                        const SkMatrix& viewMatrix) {
     using namespace GrDefaultGeoProcFactory;
     *texOffset = -1;
     *colorOffset = -1;
 
-    Coverage coverage(coverageIgnored ? Coverage::kNone_Type : Coverage::kSolid_Type);
-    LocalCoords localCoords(hasLocalCoords ? LocalCoords::kHasExplicit_Type
-                                           : LocalCoords::kUsePosition_Type);
+    LocalCoords::Type localCoordsType =
+            hasLocalCoords ? LocalCoords::kHasExplicit_Type : LocalCoords::kUsePosition_Type;
     *colorOffset = sizeof(SkPoint);
     if (hasLocalCoords) {
         *texOffset = sizeof(SkPoint) + sizeof(GrColor);
     }
-    return GrDefaultGeoProcFactory::Make(Color(Color::kAttribute_Type), coverage, localCoords,
-                                         viewMatrix);
+    return GrDefaultGeoProcFactory::Make(Color::kAttribute_Type, Coverage::kSolid_Type,
+                                         localCoordsType, viewMatrix);
 }
 
 GrDrawVerticesOp::GrDrawVerticesOp(GrColor color, GrPrimitiveType primitiveType,
@@ -88,7 +86,6 @@
         fMeshes[0].fColors.reset();
         fVariableColor = false;
     }
-    fCoverageIgnored = !optimizations.readsCoverage();
     if (!optimizations.readsLocalCoords()) {
         fMeshes[0].fLocalCoords.reset();
     }
@@ -97,8 +94,8 @@
 void GrDrawVerticesOp::onPrepareDraws(Target* target) const {
     bool hasLocalCoords = !fMeshes[0].fLocalCoords.isEmpty();
     int colorOffset = -1, texOffset = -1;
-    sk_sp<GrGeometryProcessor> gp(set_vertex_attributes(hasLocalCoords, &colorOffset, &texOffset,
-                                                        fViewMatrix, fCoverageIgnored));
+    sk_sp<GrGeometryProcessor> gp(
+            set_vertex_attributes(hasLocalCoords, &colorOffset, &texOffset, fViewMatrix));
     size_t vertexStride = gp->getVertexStride();
 
     SkASSERT(vertexStride ==
diff --git a/src/gpu/ops/GrDrawVerticesOp.h b/src/gpu/ops/GrDrawVerticesOp.h
index 2c1580d..680b41f 100644
--- a/src/gpu/ops/GrDrawVerticesOp.h
+++ b/src/gpu/ops/GrDrawVerticesOp.h
@@ -22,14 +22,14 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrDrawOp> Make(GrColor color, GrPrimitiveType primitiveType,
-                                const SkMatrix& viewMatrix, const SkPoint* positions,
-                                int vertexCount, const uint16_t* indices, int indexCount,
-                                const GrColor* colors, const SkPoint* localCoords,
-                                const SkRect& bounds) {
-        return sk_sp<GrDrawOp>(new GrDrawVerticesOp(color, primitiveType, viewMatrix, positions,
-                                                    vertexCount, indices, indexCount, colors,
-                                                    localCoords, bounds));
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, GrPrimitiveType primitiveType,
+                                          const SkMatrix& viewMatrix, const SkPoint* positions,
+                                          int vertexCount, const uint16_t* indices, int indexCount,
+                                          const GrColor* colors, const SkPoint* localCoords,
+                                          const SkRect& bounds) {
+        return std::unique_ptr<GrDrawOp>(
+                new GrDrawVerticesOp(color, primitiveType, viewMatrix, positions, vertexCount,
+                                     indices, indexCount, colors, localCoords, bounds));
     }
 
     const char* name() const override { return "DrawVerticesOp"; }
@@ -75,7 +75,6 @@
     bool fVariableColor;
     int fVertexCount;
     int fIndexCount;
-    bool fCoverageIgnored;  // comes from applyPipelineOptimizations.
 
     SkSTArray<1, Mesh, true> fMeshes;
 
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index af83aa6..93c6f0f 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -15,12 +15,10 @@
 #include "SkLatticeIter.h"
 #include "SkRect.h"
 
-static sk_sp<GrGeometryProcessor> create_gp(bool readsCoverage) {
+static sk_sp<GrGeometryProcessor> create_gp() {
     using namespace GrDefaultGeoProcFactory;
-    Color color(Color::kAttribute_Type);
-    Coverage coverage(readsCoverage ? Coverage::kSolid_Type : Coverage::kNone_Type);
-    LocalCoords localCoords(LocalCoords::kHasExplicit_Type);
-    return GrDefaultGeoProcFactory::Make(color, coverage, localCoords, SkMatrix::I());
+    return GrDefaultGeoProcFactory::Make(Color::kAttribute_Type, Coverage::kSolid_Type,
+                                         LocalCoords::kHasExplicit_Type, SkMatrix::I());
 }
 
 class NonAALatticeOp final : public GrMeshDrawOp {
@@ -70,11 +68,10 @@
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& analysioptimizations) override {
         analysioptimizations.getOverrideColorIfSet(&fPatches[0].fColor);
-        fOptimizations = analysioptimizations;
     }
 
     void onPrepareDraws(Target* target) const override {
-        sk_sp<GrGeometryProcessor> gp(create_gp(fOptimizations.readsCoverage()));
+        sk_sp<GrGeometryProcessor> gp(create_gp());
         if (!gp) {
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
@@ -148,13 +145,6 @@
         SkASSERT(this->fImageWidth == that->fImageWidth &&
                  this->fImageHeight == that->fImageHeight);
 
-        // In the event of two ops, one who can tweak, one who cannot, we just fall back to not
-        // tweaking.
-        if (fOptimizations.canTweakAlphaForCoverage() &&
-            !that->fOptimizations.canTweakAlphaForCoverage()) {
-            fOptimizations = that->fOptimizations;
-        }
-
         fPatches.move_back_n(that->fPatches.count(), that->fPatches.begin());
         this->joinBounds(*that);
         return true;
@@ -167,7 +157,6 @@
         GrColor fColor;
     };
 
-    GrPipelineOptimizations fOptimizations;
     int fImageWidth;
     int fImageHeight;
     SkSTArray<1, Patch, true> fPatches;
@@ -176,9 +165,10 @@
 };
 
 namespace GrLatticeOp {
-sk_sp<GrDrawOp> MakeNonAA(GrColor color, const SkMatrix& viewMatrix, int imageWidth,
-                          int imageHeight, std::unique_ptr<SkLatticeIter> iter, const SkRect& dst) {
-    return sk_sp<GrDrawOp>(
+std::unique_ptr<GrDrawOp> MakeNonAA(GrColor color, const SkMatrix& viewMatrix, int imageWidth,
+                                    int imageHeight, std::unique_ptr<SkLatticeIter> iter,
+                                    const SkRect& dst) {
+    return std::unique_ptr<GrDrawOp>(
             new NonAALatticeOp(color, viewMatrix, imageWidth, imageHeight, std::move(iter), dst));
 }
 };
diff --git a/src/gpu/ops/GrLatticeOp.h b/src/gpu/ops/GrLatticeOp.h
index 2908461..2ed3b9c 100644
--- a/src/gpu/ops/GrLatticeOp.h
+++ b/src/gpu/ops/GrLatticeOp.h
@@ -17,8 +17,9 @@
 struct SkRect;
 
 namespace GrLatticeOp {
-sk_sp<GrDrawOp> MakeNonAA(GrColor color, const SkMatrix& viewMatrix, int imageWidth,
-                          int imageHeight, std::unique_ptr<SkLatticeIter> iter, const SkRect& dst);
+std::unique_ptr<GrDrawOp> MakeNonAA(GrColor color, const SkMatrix& viewMatrix, int imageWidth,
+                                    int imageHeight, std::unique_ptr<SkLatticeIter> iter,
+                                    const SkRect& dst);
 };
 
 #endif
diff --git a/src/gpu/ops/GrMSAAPathRenderer.cpp b/src/gpu/ops/GrMSAAPathRenderer.cpp
index 1a73ca2..5c1c110 100644
--- a/src/gpu/ops/GrMSAAPathRenderer.cpp
+++ b/src/gpu/ops/GrMSAAPathRenderer.cpp
@@ -16,6 +16,7 @@
 #include "GrPathStencilSettings.h"
 #include "GrPathUtils.h"
 #include "GrPipelineBuilder.h"
+#include "SkAutoMalloc.h"
 #include "SkGeometry.h"
 #include "SkTraceEvent.h"
 #include "gl/GrGLVaryingHandler.h"
@@ -217,8 +218,8 @@
 class MSAAPathOp final : public GrMeshDrawOp {
 public:
     DEFINE_OP_CLASS_ID
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkPath& path, const SkMatrix& viewMatrix,
-                                const SkRect& devBounds) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkPath& path,
+                                          const SkMatrix& viewMatrix, const SkRect& devBounds) {
         int contourCount;
         int maxLineVertices;
         int maxQuadVertices;
@@ -229,8 +230,8 @@
             return nullptr;
         }
 
-        return sk_sp<GrDrawOp>(new MSAAPathOp(color, path, viewMatrix, devBounds, maxLineVertices,
-                                              maxQuadVertices, isIndexed));
+        return std::unique_ptr<GrDrawOp>(new MSAAPathOp(
+                color, path, viewMatrix, devBounds, maxLineVertices, maxQuadVertices, isIndexed));
     }
 
     const char* name() const override { return "MSAAPathOp"; }
@@ -264,9 +265,6 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fPaths[0].fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fPaths[0].fColor);
     }
 
@@ -349,7 +347,7 @@
 
         MSAAQuadVertices quads;
         size_t quadVertexStride = sizeof(MSAAQuadVertices::Vertex);
-        SkAutoFree quadVertexPtr(sk_malloc_throw(fMaxQuadVertices * quadVertexStride));
+        SkAutoMalloc quadVertexPtr(fMaxQuadVertices * quadVertexStride);
         quads.vertices = (MSAAQuadVertices::Vertex*) quadVertexPtr.get();
         quads.nextVertex = quads.vertices;
         SkDEBUGCODE(quads.verticesEnd = quads.vertices + fMaxQuadVertices;)
@@ -372,7 +370,7 @@
         SkAutoFree quadIndexPtr;
         if (fIsIndexed) {
             quads.indices = (uint16_t*)sk_malloc_throw(3 * fMaxQuadVertices * sizeof(uint16_t));
-            quadIndexPtr.set(quads.indices);
+            quadIndexPtr.reset(quads.indices);
             quads.nextIndex = quads.indices;
         } else {
             quads.indices = nullptr;
@@ -404,7 +402,7 @@
             {
                 using namespace GrDefaultGeoProcFactory;
                 lineGP = GrDefaultGeoProcFactory::Make(Color(Color::kAttribute_Type),
-                                                       Coverage(255),
+                                                       Coverage::kSolid_Type,
                                                        LocalCoords(LocalCoords::kUnused_Type),
                                                        fViewMatrix);
             }
@@ -565,7 +563,7 @@
 };
 
 bool GrMSAAPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext,
-                                          const GrPaint& paint,
+                                          GrPaint&& paint,
                                           GrAAType aaType,
                                           const GrUserStencilSettings& userStencilSettings,
                                           const GrClip& clip,
@@ -576,21 +574,15 @@
     SkPath path;
     shape.asPath(&path);
 
-    static const int kMaxNumPasses = 2;
-
-    int                          passCount = 0;
-    const GrUserStencilSettings* passes[kMaxNumPasses];
+    const GrUserStencilSettings* passes[2] = {nullptr, nullptr};
     bool                         reverse = false;
-    bool                         lastPassIsBounds;
 
     if (single_pass_shape(shape)) {
-        passCount = 1;
         if (stencilOnly) {
             passes[0] = &gDirectToStencil;
         } else {
             passes[0] = &userStencilSettings;
         }
-        lastPassIsBounds = false;
     } else {
         switch (path.getFillType()) {
             case SkPath::kInverseEvenOdd_FillType:
@@ -598,17 +590,8 @@
                 // fallthrough
             case SkPath::kEvenOdd_FillType:
                 passes[0] = &gEOStencilPass;
-                if (stencilOnly) {
-                    passCount = 1;
-                    lastPassIsBounds = false;
-                } else {
-                    passCount = 2;
-                    lastPassIsBounds = true;
-                    if (reverse) {
-                        passes[1] = &gInvEOColorPass;
-                    } else {
-                        passes[1] = &gEOColorPass;
-                    }
+                if (!stencilOnly) {
+                    passes[1] = reverse ? &gInvEOColorPass : &gEOColorPass;
                 }
                 break;
 
@@ -617,17 +600,8 @@
                 // fallthrough
             case SkPath::kWinding_FillType:
                 passes[0] = &gWindStencilSeparateWithWrap;
-                passCount = 2;
-                if (stencilOnly) {
-                    lastPassIsBounds = false;
-                    passCount = 1;
-                } else {
-                    lastPassIsBounds = true;
-                    if (reverse) {
-                        passes[1] = &gInvWindColorPass;
-                    } else {
-                        passes[1] = &gWindColorPass;
-                    }
+                if (!stencilOnly) {
+                    passes[1] = reverse ? &gInvWindColorPass : &gWindColorPass;
                 }
                 break;
             default:
@@ -639,49 +613,48 @@
     SkRect devBounds;
     GetPathDevBounds(path, renderTargetContext->width(), renderTargetContext->height(), viewMatrix,
                      &devBounds);
+    SkASSERT(passes[0]);
 
-    SkASSERT(passCount <= kMaxNumPasses);
+    std::unique_ptr<GrDrawOp> op = MSAAPathOp::Make(paint.getColor(), path, viewMatrix, devBounds);
+    if (!op) {
+        return false;
+    }
+    // If we have a cover pass then we ignore the paint in the first pass and apply it in the
+    // second.
+    GrPipelineBuilder pipelineBuilder(GrPaint::MoveOrNew(paint, passes[1]), aaType);
+    pipelineBuilder.setUserStencil(passes[0]);
+    if (passes[1]) {
+        pipelineBuilder.setDisableColorXPFactory();
+    }
+    renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
 
-    for (int p = 0; p < passCount; ++p) {
-        if (lastPassIsBounds && (p == passCount-1)) {
-            SkRect bounds;
-            SkMatrix localMatrix = SkMatrix::I();
-            if (reverse) {
-                // draw over the dev bounds (which will be the whole dst surface for inv fill).
-                bounds = devBounds;
-                SkMatrix vmi;
-                // mapRect through persp matrix may not be correct
-                if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
-                    vmi.mapRect(&bounds);
-                } else {
-                    if (!viewMatrix.invert(&localMatrix)) {
-                        return false;
-                    }
-                }
+    if (passes[1]) {
+        SkRect bounds;
+        SkMatrix localMatrix = SkMatrix::I();
+        if (reverse) {
+            // draw over the dev bounds (which will be the whole dst surface for inv fill).
+            bounds = devBounds;
+            SkMatrix vmi;
+            // mapRect through persp matrix may not be correct
+            if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
+                vmi.mapRect(&bounds);
             } else {
-                bounds = path.getBounds();
+                if (!viewMatrix.invert(&localMatrix)) {
+                    return false;
+                }
             }
-            const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
-                                                                               viewMatrix;
-            sk_sp<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(paint.getColor(), viewM, bounds,
-                                                              nullptr, &localMatrix));
-
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
-            pipelineBuilder.setUserStencil(passes[p]);
-
-            renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
         } else {
-            sk_sp<GrDrawOp> op = MSAAPathOp::Make(paint.getColor(), path, viewMatrix, devBounds);
-            if (!op) {
-                return false;
-            }
-            GrPipelineBuilder pipelineBuilder(paint, aaType);
-            pipelineBuilder.setUserStencil(passes[p]);
-            if (passCount > 1) {
-                pipelineBuilder.setDisableColorXPFactory();
-            }
-            renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
+            bounds = path.getBounds();
         }
+        const SkMatrix& viewM =
+                (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() : viewMatrix;
+        std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(paint.getColor(), viewM, bounds,
+                                                                    nullptr, &localMatrix));
+
+        GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
+        pipelineBuilder.setUserStencil(passes[1]);
+
+        renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
     }
     return true;
 }
@@ -704,7 +677,7 @@
         shape = tmpShape.get();
     }
     return this->internalDrawPath(args.fRenderTargetContext,
-                                  *args.fPaint,
+                                  std::move(args.fPaint),
                                   args.fAAType,
                                   *args.fUserStencilSettings,
                                   *args.fClip,
@@ -720,9 +693,9 @@
     SkASSERT(!args.fShape->mayBeInverseFilledAfterStyling());
 
     GrPaint paint;
-    paint.setXPFactory(GrDisableColorXPFactory::Make());
+    paint.setXPFactory(GrDisableColorXPFactory::Get());
 
-    this->internalDrawPath(args.fRenderTargetContext, paint, args.fAAType,
+    this->internalDrawPath(args.fRenderTargetContext, std::move(paint), args.fAAType,
                            GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix,
                            *args.fShape, true);
 }
diff --git a/src/gpu/ops/GrMSAAPathRenderer.h b/src/gpu/ops/GrMSAAPathRenderer.h
index a112c62..13d3e15 100644
--- a/src/gpu/ops/GrMSAAPathRenderer.h
+++ b/src/gpu/ops/GrMSAAPathRenderer.h
@@ -22,7 +22,7 @@
     void onStencilPath(const StencilPathArgs&) override;
 
     bool internalDrawPath(GrRenderTargetContext*,
-                          const GrPaint&,
+                          GrPaint&&,
                           GrAAType,
                           const GrUserStencilSettings&,
                           const GrClip&,
diff --git a/src/gpu/ops/GrNonAAFillRectOp.cpp b/src/gpu/ops/GrNonAAFillRectOp.cpp
index afef8e6..eb2143d 100644
--- a/src/gpu/ops/GrNonAAFillRectOp.cpp
+++ b/src/gpu/ops/GrNonAAFillRectOp.cpp
@@ -28,13 +28,10 @@
 
     The vertex attrib order is always pos, color, [local coords].
  */
-static sk_sp<GrGeometryProcessor> make_gp(bool readsCoverage) {
+static sk_sp<GrGeometryProcessor> make_gp() {
     using namespace GrDefaultGeoProcFactory;
-    Color color(Color::kAttribute_Type);
-    Coverage coverage(readsCoverage ? Coverage::kSolid_Type : Coverage::kNone_Type);
-
-    LocalCoords localCoords(LocalCoords::kHasExplicit_Type);
-    return GrDefaultGeoProcFactory::Make(color, coverage, localCoords, SkMatrix::I());
+    return GrDefaultGeoProcFactory::Make(Color::kAttribute_Type, Coverage::kSolid_Type,
+                                         LocalCoords::kHasExplicit_Type, SkMatrix::I());
 }
 
 static void tesselate(intptr_t vertices,
@@ -120,11 +117,10 @@
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
         optimizations.getOverrideColorIfSet(&fRects[0].fColor);
-        fOptimizations = optimizations;
     }
 
     void onPrepareDraws(Target* target) const override {
-        sk_sp<GrGeometryProcessor> gp = make_gp(fOptimizations.readsCoverage());
+        sk_sp<GrGeometryProcessor> gp = make_gp();
         if (!gp) {
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
@@ -161,13 +157,6 @@
             return false;
         }
 
-        // In the event of two ops, one who can tweak, one who cannot, we just fall back to not
-        // tweaking.
-        if (fOptimizations.canTweakAlphaForCoverage() &&
-            !that->fOptimizations.canTweakAlphaForCoverage()) {
-            fOptimizations = that->fOptimizations;
-        }
-
         fRects.push_back_n(that->fRects.count(), that->fRects.begin());
         this->joinBounds(*that);
         return true;
@@ -180,7 +169,6 @@
         GrQuad fLocalQuad;
     };
 
-    GrPipelineOptimizations fOptimizations;
     SkSTArray<1, RectInfo, true> fRects;
 
     typedef GrMeshDrawOp INHERITED;
@@ -188,12 +176,13 @@
 
 namespace GrNonAAFillRectOp {
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect,
-                     const SkRect* localRect,
-                     const SkMatrix* localMatrix) {
-    return sk_sp<GrDrawOp>(new NonAAFillRectOp(color, viewMatrix, rect, localRect, localMatrix));
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkRect* localRect,
+                               const SkMatrix* localMatrix) {
+    return std::unique_ptr<GrDrawOp>(
+            new NonAAFillRectOp(color, viewMatrix, rect, localRect, localMatrix));
 }
 };
 
diff --git a/src/gpu/ops/GrNonAAFillRectOp.h b/src/gpu/ops/GrNonAAFillRectOp.h
index 3d983e1..e699221 100644
--- a/src/gpu/ops/GrNonAAFillRectOp.h
+++ b/src/gpu/ops/GrNonAAFillRectOp.h
@@ -17,17 +17,17 @@
 
 namespace GrNonAAFillRectOp {
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect,
-                     const SkRect* localRect,
-                     const SkMatrix* localMatrix);
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkRect* localRect,
+                               const SkMatrix* localMatrix);
 
-sk_sp<GrDrawOp> MakeWithPerspective(GrColor color,
-                                    const SkMatrix& viewMatrix,
-                                    const SkRect& rect,
-                                    const SkRect* localRect,
-                                    const SkMatrix* localMatrix);
+std::unique_ptr<GrDrawOp> MakeWithPerspective(GrColor color,
+                                              const SkMatrix& viewMatrix,
+                                              const SkRect& rect,
+                                              const SkRect* localRect,
+                                              const SkMatrix* localMatrix);
 };
 
 #endif
diff --git a/src/gpu/ops/GrNonAAFillRectPerspectiveOp.cpp b/src/gpu/ops/GrNonAAFillRectPerspectiveOp.cpp
index 9b6a857..b919a0d 100644
--- a/src/gpu/ops/GrNonAAFillRectPerspectiveOp.cpp
+++ b/src/gpu/ops/GrNonAAFillRectPerspectiveOp.cpp
@@ -27,14 +27,11 @@
     The vertex attrib order is always pos, color, [local coords].
  */
 static sk_sp<GrGeometryProcessor> make_persp_gp(const SkMatrix& viewMatrix,
-                                                bool readsCoverage,
                                                 bool hasExplicitLocalCoords,
                                                 const SkMatrix* localMatrix) {
     SkASSERT(viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective()));
 
     using namespace GrDefaultGeoProcFactory;
-    Color color(Color::kAttribute_Type);
-    Coverage coverage(readsCoverage ? Coverage::kSolid_Type : Coverage::kNone_Type);
 
     // 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).
@@ -44,14 +41,16 @@
         LocalCoords localCoords(hasExplicitLocalCoords ? LocalCoords::kHasExplicit_Type
                                                        : LocalCoords::kUsePosition_Type,
                                 localMatrix);
-        return GrDefaultGeoProcFactory::Make(color, coverage, localCoords, viewMatrix);
+        return GrDefaultGeoProcFactory::Make(Color::kAttribute_Type, Coverage::kSolid_Type,
+                                             localCoords, viewMatrix);
     } else if (hasExplicitLocalCoords) {
         LocalCoords localCoords(LocalCoords::kHasExplicit_Type, localMatrix);
-        return GrDefaultGeoProcFactory::Make(color, coverage, localCoords, SkMatrix::I());
+        return GrDefaultGeoProcFactory::Make(Color::kAttribute_Type, Coverage::kSolid_Type,
+                                             localCoords, SkMatrix::I());
     } else {
         LocalCoords localCoords(LocalCoords::kUsePosition_Type, localMatrix);
-        return GrDefaultGeoProcFactory::MakeForDeviceSpace(color, coverage, localCoords,
-                                                           viewMatrix);
+        return GrDefaultGeoProcFactory::MakeForDeviceSpace(
+                Color::kAttribute_Type, Coverage::kSolid_Type, localCoords, viewMatrix);
     }
 }
 
@@ -137,12 +136,10 @@
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
         optimizations.getOverrideColorIfSet(&fRects[0].fColor);
-        fOptimizations = optimizations;
     }
 
     void onPrepareDraws(Target* target) const override {
         sk_sp<GrGeometryProcessor> gp = make_persp_gp(fViewMatrix,
-                                                      fOptimizations.readsCoverage(),
                                                       fHasLocalRect,
                                                       fHasLocalMatrix ? &fLocalMatrix : nullptr);
         if (!gp) {
@@ -200,13 +197,6 @@
             return false;
         }
 
-        // In the event of two ops, one who can tweak, one who cannot, we just fall back to not
-        // tweaking.
-        if (fOptimizations.canTweakAlphaForCoverage() &&
-            !that->fOptimizations.canTweakAlphaForCoverage()) {
-            fOptimizations = that->fOptimizations;
-        }
-
         fRects.push_back_n(that->fRects.count(), that->fRects.begin());
         this->joinBounds(*that);
         return true;
@@ -218,7 +208,6 @@
         SkRect fLocalRect;
     };
 
-    GrPipelineOptimizations fOptimizations;
     SkSTArray<1, RectInfo, true> fRects;
     bool fHasLocalMatrix;
     bool fHasLocalRect;
@@ -230,12 +219,12 @@
 
 namespace GrNonAAFillRectOp {
 
-sk_sp<GrDrawOp> MakeWithPerspective(GrColor color,
-                                    const SkMatrix& viewMatrix,
-                                    const SkRect& rect,
-                                    const SkRect* localRect,
-                                    const SkMatrix* localMatrix) {
-    return sk_sp<GrDrawOp>(
+std::unique_ptr<GrDrawOp> MakeWithPerspective(GrColor color,
+                                              const SkMatrix& viewMatrix,
+                                              const SkRect& rect,
+                                              const SkRect* localRect,
+                                              const SkMatrix* localMatrix) {
+    return std::unique_ptr<GrDrawOp>(
             new NonAAFillRectPerspectiveOp(color, viewMatrix, rect, localRect, localMatrix));
 }
 };
diff --git a/src/gpu/ops/GrNonAAStrokeRectOp.cpp b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
index 1600241..417d76e 100644
--- a/src/gpu/ops/GrNonAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
@@ -62,8 +62,9 @@
         return string;
     }
 
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect,
-                                const SkStrokeRec& stroke, bool snapToPixelCenters) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix,
+                                          const SkRect& rect, const SkStrokeRec& stroke,
+                                          bool snapToPixelCenters) {
         if (!allowed_stroke(stroke)) {
             return nullptr;
         }
@@ -94,7 +95,7 @@
         } else {
             op->setTransformedBounds(bounds, op->fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
         }
-        return sk_sp<GrDrawOp>(op);
+        return std::unique_ptr<GrDrawOp>(op);
     }
 
 private:
@@ -110,12 +111,11 @@
         {
             using namespace GrDefaultGeoProcFactory;
             Color color(fColor);
-            Coverage coverage(fOptimizations.readsCoverage() ? Coverage::kSolid_Type
-                                                             : Coverage::kNone_Type);
-            LocalCoords localCoords(fOptimizations.readsLocalCoords()
-                                            ? LocalCoords::kUsePosition_Type
-                                            : LocalCoords::kUnused_Type);
-            gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, fViewMatrix);
+            LocalCoords::Type localCoordsType = fNeedsLocalCoords
+                                                        ? LocalCoords::kUsePosition_Type
+                                                        : LocalCoords::kUnused_Type;
+            gp = GrDefaultGeoProcFactory::Make(color, Coverage::kSolid_Type, localCoordsType,
+                                               fViewMatrix);
         }
 
         size_t vertexStride = gp->getVertexStride();
@@ -161,7 +161,7 @@
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
         optimizations.getOverrideColorIfSet(&fColor);
-        fOptimizations = optimizations;
+        fNeedsLocalCoords = optimizations.readsLocalCoords();
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps&) override {
@@ -174,7 +174,7 @@
     SkMatrix fViewMatrix;
     SkRect fRect;
     SkScalar fStrokeWidth;
-    GrPipelineOptimizations fOptimizations;
+    bool fNeedsLocalCoords;
 
     const static int kVertsPerHairlineRect = 5;
     const static int kVertsPerStrokeRect = 10;
@@ -184,11 +184,11 @@
 
 namespace GrNonAAStrokeRectOp {
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect,
-                     const SkStrokeRec& stroke,
-                     bool snapToPixelCenters) {
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkStrokeRec& stroke,
+                               bool snapToPixelCenters) {
     return NonAAStrokeRectOp::Make(color, viewMatrix, rect, stroke, snapToPixelCenters);
 }
 }
diff --git a/src/gpu/ops/GrNonAAStrokeRectOp.h b/src/gpu/ops/GrNonAAStrokeRectOp.h
index 1e067fa..9c46f40 100644
--- a/src/gpu/ops/GrNonAAStrokeRectOp.h
+++ b/src/gpu/ops/GrNonAAStrokeRectOp.h
@@ -18,11 +18,11 @@
 
 namespace GrNonAAStrokeRectOp {
 
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRect& rect,
-                     const SkStrokeRec&,
-                     bool snapToPixelCenters);
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkStrokeRec&,
+                               bool snapToPixelCenters);
 }
 
 #endif
diff --git a/src/gpu/ops/GrOp.h b/src/gpu/ops/GrOp.h
index 47a8097..3a37ad5 100644
--- a/src/gpu/ops/GrOp.h
+++ b/src/gpu/ops/GrOp.h
@@ -56,7 +56,7 @@
         return kClassID; \
     }
 
-class GrOp : public GrNonAtomicRef<GrOp> {
+class GrOp : private SkNoncopyable {
 public:
     GrOp(uint32_t classID);
     virtual ~GrOp();
@@ -129,11 +129,6 @@
     /** Issues the op's commands to GrGpu. */
     void execute(GrOpFlushState* state, const SkRect& bounds) { this->onExecute(state, bounds); }
 
-    /** Used to block combining across render target changes. Remove this once we store
-        GrOps for different RTs in different targets. */
-    // TODO: this needs to be updated to return GrSurfaceProxy::UniqueID
-    virtual GrGpuResource::UniqueID renderTargetUniqueID() const = 0;
-
     /** Used for spewing information about ops when debugging. */
     virtual SkString dumpInfo() const {
         SkString string;
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index 5a9a077..f693169 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -613,9 +613,9 @@
         SkScalar fSweepAngleRadians;
         bool fUseCenter;
     };
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, SkPoint center,
-                                SkScalar radius, const GrStyle& style,
-                                const ArcParams* arcParams = nullptr) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, SkPoint center,
+                                          SkScalar radius, const GrStyle& style,
+                                          const ArcParams* arcParams = nullptr) {
         SkASSERT(circle_stays_circle(viewMatrix));
         const SkStrokeRec& stroke = style.strokeRec();
         if (style.hasPathEffect()) {
@@ -672,7 +672,7 @@
         outerRadius += SK_ScalarHalf;
         innerRadius -= SK_ScalarHalf;
         bool stroked = isStrokeOnly && innerRadius > 0.0f;
-        sk_sp<CircleOp> op(new CircleOp());
+        std::unique_ptr<CircleOp> op(new CircleOp());
         op->fViewMatrixIfUsingLocalCoords = viewMatrix;
 
         // This makes every point fully inside the intersection plane.
@@ -1148,8 +1148,8 @@
 class EllipseOp : public GrMeshDrawOp {
 public:
     DEFINE_OP_CLASS_ID
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRect& ellipse,
-                                const SkStrokeRec& stroke) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix,
+                                          const SkRect& ellipse, const SkStrokeRec& stroke) {
         SkASSERT(viewMatrix.rectStaysRect());
 
         // do any matrix crunching before we reset the draw state for device coords
@@ -1208,7 +1208,7 @@
             yRadius += scaledStroke.fY;
         }
 
-        sk_sp<EllipseOp> op(new EllipseOp());
+        std::unique_ptr<EllipseOp> op(new EllipseOp());
         op->fGeoData.emplace_back(
                 Geometry{color, xRadius, yRadius, innerXRadius, innerYRadius,
                          SkRect::MakeLTRB(center.fX - xRadius, center.fY - yRadius,
@@ -1251,9 +1251,6 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsCoverage()) {
-            fGeoData[0].fColor = GrColor_ILLEGAL;
-        }
         if (!optimizations.readsLocalCoords()) {
             fViewMatrixIfUsingLocalCoords.reset();
         }
@@ -1370,10 +1367,10 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrDrawOp> Make(GrColor color,
-                                const SkMatrix& viewMatrix,
-                                const SkRect& ellipse,
-                                const SkStrokeRec& stroke) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color,
+                                          const SkMatrix& viewMatrix,
+                                          const SkRect& ellipse,
+                                          const SkStrokeRec& stroke) {
         SkPoint center = SkPoint::Make(ellipse.centerX(), ellipse.centerY());
         SkScalar xRadius = SkScalarHalf(ellipse.width());
         SkScalar yRadius = SkScalarHalf(ellipse.height());
@@ -1430,7 +1427,7 @@
         SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a * a + c * c);
         SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b * b + d * d);
 
-        sk_sp<DIEllipseOp> op(new DIEllipseOp());
+        std::unique_ptr<DIEllipseOp> op(new DIEllipseOp());
         op->fGeoData.emplace_back(Geometry{
                 viewMatrix, color, xRadius, yRadius, innerXRadius, innerYRadius, geoDx, geoDy,
                 dieStyle,
@@ -2054,9 +2051,9 @@
 
     // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates
     // whether the rrect is only stroked or stroked and filled.
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRect& devRect,
-                                float devXRadius, float devYRadius, SkVector devStrokeWidths,
-                                bool strokeOnly) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix,
+                                          const SkRect& devRect, float devXRadius, float devYRadius,
+                                          SkVector devStrokeWidths, bool strokeOnly) {
         SkASSERT(devXRadius > 0.5);
         SkASSERT(devYRadius > 0.5);
         SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0));
@@ -2101,7 +2098,7 @@
             bounds.outset(devStrokeWidths.fX, devStrokeWidths.fY);
         }
 
-        sk_sp<EllipticalRRectOp> op(new EllipticalRRectOp());
+        std::unique_ptr<EllipticalRRectOp> op(new EllipticalRRectOp());
         op->fStroked = stroked;
         op->fViewMatrixIfUsingLocalCoords = viewMatrix;
         op->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
@@ -2266,11 +2263,11 @@
     typedef GrMeshDrawOp INHERITED;
 };
 
-static sk_sp<GrDrawOp> make_rrect_op(GrColor color,
-                                     bool needsDistance,
-                                     const SkMatrix& viewMatrix,
-                                     const SkRRect& rrect,
-                                     const SkStrokeRec& stroke) {
+static std::unique_ptr<GrDrawOp> make_rrect_op(GrColor color,
+                                               bool needsDistance,
+                                               const SkMatrix& viewMatrix,
+                                               const SkRRect& rrect,
+                                               const SkStrokeRec& stroke) {
     SkASSERT(viewMatrix.rectStaysRect());
     SkASSERT(rrect.isSimple());
     SkASSERT(!rrect.isOval());
@@ -2328,8 +2325,8 @@
 
     // if the corners are circles, use the circle renderer
     if (isCircular) {
-        return sk_sp<GrDrawOp>(new CircularRRectOp(color, needsDistance, viewMatrix, bounds,
-                                                   xRadius, scaledStroke.fX, isStrokeOnly));
+        return std::unique_ptr<GrDrawOp>(new CircularRRectOp(
+                color, needsDistance, viewMatrix, bounds, xRadius, scaledStroke.fX, isStrokeOnly));
         // otherwise we use the ellipse renderer
     } else {
         return EllipticalRRectOp::Make(color, viewMatrix, bounds, xRadius, yRadius, scaledStroke,
@@ -2337,12 +2334,12 @@
     }
 }
 
-sk_sp<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrColor color,
-                                             bool needsDistance,
-                                             const SkMatrix& viewMatrix,
-                                             const SkRRect& rrect,
-                                             const SkStrokeRec& stroke,
-                                             const GrShaderCaps* shaderCaps) {
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrColor color,
+                                                       bool needsDistance,
+                                                       const SkMatrix& viewMatrix,
+                                                       const SkRRect& rrect,
+                                                       const SkStrokeRec& stroke,
+                                                       const GrShaderCaps* shaderCaps) {
     if (rrect.isOval()) {
         return MakeOvalOp(color, viewMatrix, rrect.getBounds(), stroke, shaderCaps);
     }
@@ -2356,11 +2353,11 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-sk_sp<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrColor color,
-                                            const SkMatrix& viewMatrix,
-                                            const SkRect& oval,
-                                            const SkStrokeRec& stroke,
-                                            const GrShaderCaps* shaderCaps) {
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrColor color,
+                                                      const SkMatrix& viewMatrix,
+                                                      const SkRect& oval,
+                                                      const SkStrokeRec& stroke,
+                                                      const GrShaderCaps* shaderCaps) {
     // we can draw circles
     SkScalar width = oval.width();
     if (SkScalarNearlyEqual(width, oval.height()) && circle_stays_circle(viewMatrix)) {
@@ -2383,10 +2380,11 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-sk_sp<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrColor color, const SkMatrix& viewMatrix,
-                                           const SkRect& oval, SkScalar startAngle,
-                                           SkScalar sweepAngle, bool useCenter,
-                                           const GrStyle& style, const GrShaderCaps* shaderCaps) {
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrColor color, const SkMatrix& viewMatrix,
+                                                     const SkRect& oval, SkScalar startAngle,
+                                                     SkScalar sweepAngle, bool useCenter,
+                                                     const GrStyle& style,
+                                                     const GrShaderCaps* shaderCaps) {
     SkASSERT(!oval.isEmpty());
     SkASSERT(sweepAngle);
     SkScalar width = oval.width();
@@ -2429,8 +2427,8 @@
             arcParamsTmp.fUseCenter = random->nextBool();
             arcParams = &arcParamsTmp;
         }
-        sk_sp<GrDrawOp> op = CircleOp::Make(color, viewMatrix, center, radius,
-                                            GrStyle(stroke, nullptr), arcParams);
+        std::unique_ptr<GrDrawOp> op = CircleOp::Make(color, viewMatrix, center, radius,
+                                                      GrStyle(stroke, nullptr), arcParams);
         if (op) {
             return op;
         }
diff --git a/src/gpu/ops/GrOvalOpFactory.h b/src/gpu/ops/GrOvalOpFactory.h
index dfe7ebf..0ddcc9b 100644
--- a/src/gpu/ops/GrOvalOpFactory.h
+++ b/src/gpu/ops/GrOvalOpFactory.h
@@ -24,26 +24,26 @@
  */
 class GrOvalOpFactory {
 public:
-    static sk_sp<GrDrawOp> MakeOvalOp(GrColor,
-                                      const SkMatrix& viewMatrix,
-                                      const SkRect& oval,
-                                      const SkStrokeRec& stroke,
-                                      const GrShaderCaps* shaderCaps);
-    static sk_sp<GrDrawOp> MakeRRectOp(GrColor,
-                                       bool needsDistance,
-                                       const SkMatrix& viewMatrix,
-                                       const SkRRect& rrect,
-                                       const SkStrokeRec& stroke,
-                                       const GrShaderCaps* shaderCaps);
+    static std::unique_ptr<GrDrawOp> MakeOvalOp(GrColor,
+                                                const SkMatrix& viewMatrix,
+                                                const SkRect& oval,
+                                                const SkStrokeRec& stroke,
+                                                const GrShaderCaps* shaderCaps);
+    static std::unique_ptr<GrDrawOp> MakeRRectOp(GrColor,
+                                                 bool needsDistance,
+                                                 const SkMatrix& viewMatrix,
+                                                 const SkRRect& rrect,
+                                                 const SkStrokeRec& stroke,
+                                                 const GrShaderCaps* shaderCaps);
 
-    static sk_sp<GrDrawOp> MakeArcOp(GrColor,
-                                     const SkMatrix& viewMatrix,
-                                     const SkRect& oval,
-                                     SkScalar startAngle,
-                                     SkScalar sweepAngle,
-                                     bool useCenter,
-                                     const GrStyle&,
-                                     const GrShaderCaps* shaderCaps);
+    static std::unique_ptr<GrDrawOp> MakeArcOp(GrColor,
+                                               const SkMatrix& viewMatrix,
+                                               const SkRect& oval,
+                                               SkScalar startAngle,
+                                               SkScalar sweepAngle,
+                                               bool useCenter,
+                                               const GrStyle&,
+                                               const GrShaderCaps* shaderCaps);
 };
 
 #endif  // GrOvalOpFactory_DEFINED
diff --git a/src/gpu/ops/GrPLSPathRenderer.cpp b/src/gpu/ops/GrPLSPathRenderer.cpp
index 47c54d7..545d12b 100644
--- a/src/gpu/ops/GrPLSPathRenderer.cpp
+++ b/src/gpu/ops/GrPLSPathRenderer.cpp
@@ -634,7 +634,6 @@
 
     const Attribute* inPosition() const { return fInPosition; }
     GrColor color() const { return fColor; }
-    bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
 
@@ -683,10 +682,8 @@
             fsBuilder->codeAppend("coverage += pls.windings[2] != 0 ? 0.25 : 0.0;");
             fsBuilder->codeAppend("coverage += pls.windings[3] != 0 ? 0.25 : 0.0;");
             fsBuilder->codeAppend("}");
-            if (!fe.colorIgnored()) {
-                this->setupUniformColor(fsBuilder, uniformHandler, args.fOutputColor,
-                                        &fColorUniform);
-            }
+            this->setupUniformColor(fsBuilder, uniformHandler, args.fOutputColor,
+                                    &fColorUniform);
             fsBuilder->codeAppendf("%s = vec4(coverage);", args.fOutputCoverage);
             fsBuilder->codeAppendf("%s = vec4(1.0, 0.0, 1.0, 1.0);", args.fOutputColor);
         }
@@ -704,7 +701,7 @@
                      FPCoordTransformIter&& transformIter) override {
             const PLSFinishEffect& fe = gp.cast<PLSFinishEffect>();
             pdman.set1f(fUseEvenOdd, fe.fUseEvenOdd);
-            if (fe.color() != fColor && !fe.colorIgnored()) {
+            if (fe.color() != fColor) {
                 GrGLfloat c[4];
                 GrColorToRGBAFloat(fe.color(), c);
                 pdman.set4fv(fColorUniform, 1, c);
@@ -766,8 +763,9 @@
 class PLSPathOp final : public GrMeshDrawOp {
 public:
     DEFINE_OP_CLASS_ID
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkPath& path, const SkMatrix& viewMatrix) {
-        return sk_sp<GrDrawOp>(new PLSPathOp(color, path, viewMatrix));
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkPath& path,
+                                          const SkMatrix& viewMatrix) {
+        return std::unique_ptr<GrDrawOp>(new PLSPathOp(color, path, viewMatrix));
     }
 
     const char* name() const override { return "PLSPathOp"; }
@@ -795,9 +793,6 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fColor);
 
         fUsesLocalCoords = optimizations.readsLocalCoords();
@@ -931,8 +926,8 @@
     SkPath path;
     args.fShape->asPath(&path);
 
-    sk_sp<GrDrawOp> op = PLSPathOp::Make(args.fPaint->getColor(), path, *args.fViewMatrix);
-    GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fAAType);
+    std::unique_ptr<GrDrawOp> op = PLSPathOp::Make(args.fPaint.getColor(), path, *args.fViewMatrix);
+    GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
     pipelineBuilder.setUserStencil(args.fUserStencilSettings);
 
     args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(op));
diff --git a/src/gpu/ops/GrRectOpFactory.cpp b/src/gpu/ops/GrRectOpFactory.cpp
index fd73624..5c8d344 100644
--- a/src/gpu/ops/GrRectOpFactory.cpp
+++ b/src/gpu/ops/GrRectOpFactory.cpp
@@ -13,9 +13,9 @@
 
 namespace GrRectOpFactory {
 
-sk_sp<GrDrawOp> MakeAAFillNestedRects(GrColor color,
-                                      const SkMatrix& viewMatrix,
-                                      const SkRect rects[2]) {
+std::unique_ptr<GrDrawOp> MakeAAFillNestedRects(GrColor color,
+                                                const SkMatrix& viewMatrix,
+                                                const SkRect rects[2]) {
     SkASSERT(viewMatrix.rectStaysRect());
     SkASSERT(!rects[0].isEmpty() && !rects[1].isEmpty());
 
diff --git a/src/gpu/ops/GrRectOpFactory.h b/src/gpu/ops/GrRectOpFactory.h
index 90263a0..3f42008 100644
--- a/src/gpu/ops/GrRectOpFactory.h
+++ b/src/gpu/ops/GrRectOpFactory.h
@@ -27,11 +27,11 @@
  */
 namespace GrRectOpFactory {
 
-inline sk_sp<GrDrawOp> MakeNonAAFill(GrColor color,
-                                     const SkMatrix& viewMatrix,
-                                     const SkRect& rect,
-                                     const SkRect* localRect,
-                                     const SkMatrix* localMatrix) {
+inline std::unique_ptr<GrDrawOp> MakeNonAAFill(GrColor color,
+                                               const SkMatrix& viewMatrix,
+                                               const SkRect& rect,
+                                               const SkRect* localRect,
+                                               const SkMatrix* localMatrix) {
     if (viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective())) {
         return GrNonAAFillRectOp::MakeWithPerspective(color, viewMatrix, rect, localRect,
                                                       localMatrix);
@@ -40,11 +40,11 @@
     }
 }
 
-inline sk_sp<GrDrawOp> MakeAAFill(const GrPaint& paint,
-                                  const SkMatrix& viewMatrix,
-                                  const SkRect& rect,
-                                  const SkRect& croppedRect,
-                                  const SkRect& devRect) {
+inline std::unique_ptr<GrDrawOp> MakeAAFill(const GrPaint& paint,
+                                            const SkMatrix& viewMatrix,
+                                            const SkRect& rect,
+                                            const SkRect& croppedRect,
+                                            const SkRect& devRect) {
     if (!paint.usesDistanceVectorField()) {
         return GrAAFillRectOp::Make(paint.getColor(), viewMatrix, croppedRect, devRect);
     } else {
@@ -52,31 +52,32 @@
     }
 }
 
-inline sk_sp<GrDrawOp> MakeAAFill(GrColor color,
-                                  const SkMatrix& viewMatrix,
-                                  const SkMatrix& localMatrix,
-                                  const SkRect& rect,
-                                  const SkRect& devRect) {
+inline std::unique_ptr<GrDrawOp> MakeAAFill(GrColor color,
+                                            const SkMatrix& viewMatrix,
+                                            const SkMatrix& localMatrix,
+                                            const SkRect& rect,
+                                            const SkRect& devRect) {
     return GrAAFillRectOp::Make(color, viewMatrix, localMatrix, rect, devRect);
 }
 
-inline sk_sp<GrDrawOp> MakeNonAAStroke(GrColor color,
-                                       const SkMatrix& viewMatrix,
-                                       const SkRect& rect,
-                                       const SkStrokeRec& strokeRec,
-                                       bool snapToPixelCenters) {
+inline std::unique_ptr<GrDrawOp> MakeNonAAStroke(GrColor color,
+                                                 const SkMatrix& viewMatrix,
+                                                 const SkRect& rect,
+                                                 const SkStrokeRec& strokeRec,
+                                                 bool snapToPixelCenters) {
     return GrNonAAStrokeRectOp::Make(color, viewMatrix, rect, strokeRec, snapToPixelCenters);
 }
 
-inline sk_sp<GrDrawOp> MakeAAStroke(GrColor color,
-                                    const SkMatrix& viewMatrix,
-                                    const SkRect& rect,
-                                    const SkStrokeRec& stroke) {
+inline std::unique_ptr<GrDrawOp> MakeAAStroke(GrColor color,
+                                              const SkMatrix& viewMatrix,
+                                              const SkRect& rect,
+                                              const SkStrokeRec& stroke) {
     return GrAAStrokeRectOp::Make(color, viewMatrix, rect, stroke);
 }
 
 // First rect is outer; second rect is inner
-sk_sp<GrDrawOp> MakeAAFillNestedRects(GrColor, const SkMatrix& viewMatrix, const SkRect rects[2]);
+std::unique_ptr<GrDrawOp> MakeAAFillNestedRects(GrColor, const SkMatrix& viewMatrix,
+                                                const SkRect rects[2]);
 };
 
 #endif
diff --git a/src/gpu/ops/GrRegionOp.cpp b/src/gpu/ops/GrRegionOp.cpp
index 4d45d80..32a8150 100644
--- a/src/gpu/ops/GrRegionOp.cpp
+++ b/src/gpu/ops/GrRegionOp.cpp
@@ -17,13 +17,10 @@
 static const int kVertsPerInstance = 4;
 static const int kIndicesPerInstance = 6;
 
-static sk_sp<GrGeometryProcessor> make_gp(bool readsCoverage, const SkMatrix& viewMatrix) {
+static sk_sp<GrGeometryProcessor> make_gp(const SkMatrix& viewMatrix) {
     using namespace GrDefaultGeoProcFactory;
-    Color color(Color::kAttribute_Type);
-    Coverage coverage(readsCoverage ? Coverage::kSolid_Type : Coverage::kNone_Type);
-
-    LocalCoords localCoords(LocalCoords::kUsePosition_Type);
-    return GrDefaultGeoProcFactory::Make(color, coverage, localCoords, viewMatrix);
+    return GrDefaultGeoProcFactory::Make(Color::kAttribute_Type, Coverage::kSolid_Type,
+                                         LocalCoords::kUsePosition_Type, viewMatrix);
 }
 
 static void tesselate_region(intptr_t vertices,
@@ -87,11 +84,10 @@
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
         optimizations.getOverrideColorIfSet(&fRegions[0].fColor);
-        fOptimizations = optimizations;
     }
 
     void onPrepareDraws(Target* target) const override {
-        sk_sp<GrGeometryProcessor> gp = make_gp(fOptimizations.readsCoverage(), fViewMatrix);
+        sk_sp<GrGeometryProcessor> gp = make_gp(fViewMatrix);
         if (!gp) {
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
@@ -146,7 +142,6 @@
     };
 
     SkMatrix fViewMatrix;
-    GrPipelineOptimizations fOptimizations;
     SkSTArray<1, RegionInfo, true> fRegions;
 
     typedef GrMeshDrawOp INHERITED;
@@ -154,7 +149,7 @@
 
 namespace GrRegionOp {
 
-sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRegion& region) {
-    return sk_sp<GrDrawOp>(new RegionOp(color, viewMatrix, region));
+std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRegion& region) {
+    return std::unique_ptr<GrDrawOp>(new RegionOp(color, viewMatrix, region));
 }
 }
diff --git a/src/gpu/ops/GrRegionOp.h b/src/gpu/ops/GrRegionOp.h
index 2423060..e4b8f3e 100644
--- a/src/gpu/ops/GrRegionOp.h
+++ b/src/gpu/ops/GrRegionOp.h
@@ -16,7 +16,7 @@
 class SkRegion;
 
 namespace GrRegionOp {
-sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRegion& region);
+std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, const SkRegion& region);
 }
 
 #endif
diff --git a/src/gpu/ops/GrShadowRRectOp.cpp b/src/gpu/ops/GrShadowRRectOp.cpp
index b5829f4..1882aad 100755
--- a/src/gpu/ops/GrShadowRRectOp.cpp
+++ b/src/gpu/ops/GrShadowRRectOp.cpp
@@ -67,8 +67,9 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, SkPoint center,
-                                SkScalar radius, SkScalar blurRadius, const GrStyle& style) {
+    static std::unique_ptr<GrDrawOp> Make(GrColor color, const SkMatrix& viewMatrix, SkPoint center,
+                                          SkScalar radius, SkScalar blurRadius,
+                                          const GrStyle& style) {
         SkASSERT(viewMatrix.isSimilarity());
         const SkStrokeRec& stroke = style.strokeRec();
         if (style.hasPathEffect()) {
@@ -108,7 +109,7 @@
         outerRadius += SK_ScalarHalf;
         innerRadius -= SK_ScalarHalf;
         bool stroked = isStrokeOnly && innerRadius > 0.0f;
-        sk_sp<ShadowCircleOp> op(new ShadowCircleOp());
+        std::unique_ptr<ShadowCircleOp> op(new ShadowCircleOp());
         op->fViewMatrixIfUsingLocalCoords = viewMatrix;
 
         SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
@@ -826,12 +827,12 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-sk_sp<GrDrawOp> make_shadow_circle_op(GrColor color,
-                                      const SkMatrix& viewMatrix,
-                                      const SkRect& oval,
-                                      SkScalar blurRadius,
-                                      const SkStrokeRec& stroke,
-                                      const GrShaderCaps* shaderCaps) {
+std::unique_ptr<GrDrawOp> make_shadow_circle_op(GrColor color,
+                                                const SkMatrix& viewMatrix,
+                                                const SkRect& oval,
+                                                SkScalar blurRadius,
+                                                const SkStrokeRec& stroke,
+                                                const GrShaderCaps* shaderCaps) {
     // we can only draw circles
     SkScalar width = oval.width();
     SkASSERT(SkScalarNearlyEqual(width, oval.height()) && viewMatrix.isSimilarity());
@@ -840,11 +841,11 @@
                                 GrStyle(stroke, nullptr));
 }
 
-static sk_sp<GrDrawOp> make_shadow_rrect_op(GrColor color,
-                                            const SkMatrix& viewMatrix,
-                                            const SkRRect& rrect,
-                                            SkScalar blurRadius,
-                                            const SkStrokeRec& stroke) {
+static std::unique_ptr<GrDrawOp> make_shadow_rrect_op(GrColor color,
+                                                      const SkMatrix& viewMatrix,
+                                                      const SkRRect& rrect,
+                                                      SkScalar blurRadius,
+                                                      const SkStrokeRec& stroke) {
     SkASSERT(viewMatrix.rectStaysRect());
     SkASSERT(rrect.isSimple());
     SkASSERT(!rrect.isOval());
@@ -897,17 +898,17 @@
         return nullptr;
     }
 
-    return sk_sp<GrDrawOp>(new ShadowCircularRRectOp(color, viewMatrix, bounds, xRadius, blurRadius,
-                                                     scaledStroke.fX, isStrokeOnly));
+    return std::unique_ptr<GrDrawOp>(new ShadowCircularRRectOp(
+            color, viewMatrix, bounds, xRadius, blurRadius, scaledStroke.fX, isStrokeOnly));
 }
 
 namespace GrShadowRRectOp {
-sk_sp<GrDrawOp> Make(GrColor color,
-                     const SkMatrix& viewMatrix,
-                     const SkRRect& rrect,
-                     const SkScalar blurRadius,
-                     const SkStrokeRec& stroke,
-                     const GrShaderCaps* shaderCaps) {
+std::unique_ptr<GrDrawOp> Make(GrColor color,
+                               const SkMatrix& viewMatrix,
+                               const SkRRect& rrect,
+                               const SkScalar blurRadius,
+                               const SkStrokeRec& stroke,
+                               const GrShaderCaps* shaderCaps) {
     if (rrect.isOval()) {
         return make_shadow_circle_op(color, viewMatrix, rrect.getBounds(), blurRadius, stroke,
                                      shaderCaps);
@@ -940,8 +941,8 @@
         SkScalar radius = circle.width() / 2.f;
         SkStrokeRec stroke = GrTest::TestStrokeRec(random);
         SkScalar blurRadius = random->nextSScalar1() * 72.f;
-        sk_sp<GrDrawOp> op = ShadowCircleOp::Make(color, viewMatrix, center, radius, blurRadius,
-                                                  GrStyle(stroke, nullptr));
+        std::unique_ptr<GrDrawOp> op = ShadowCircleOp::Make(color, viewMatrix, center, radius,
+                                                            blurRadius, GrStyle(stroke, nullptr));
         if (op) {
             return op;
         }
diff --git a/src/gpu/ops/GrShadowRRectOp.h b/src/gpu/ops/GrShadowRRectOp.h
index 3508cb8..50e0102 100755
--- a/src/gpu/ops/GrShadowRRectOp.h
+++ b/src/gpu/ops/GrShadowRRectOp.h
@@ -19,9 +19,9 @@
 
 namespace GrShadowRRectOp {
 
-sk_sp<GrDrawOp> Make(GrColor, const SkMatrix& viewMatrix, const SkRRect& rrect,
-                     const SkScalar blurRadius, const SkStrokeRec& stroke,
-                     const GrShaderCaps* shaderCaps);
+std::unique_ptr<GrDrawOp> Make(GrColor, const SkMatrix& viewMatrix, const SkRRect& rrect,
+                               const SkScalar blurRadius, const SkStrokeRec& stroke,
+                               const GrShaderCaps* shaderCaps);
 }
 
 #endif
diff --git a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
index 16765e0..77d8d23 100644
--- a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
+++ b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
@@ -112,8 +112,8 @@
         }
         const SkMatrix& viewM = viewMatrix.hasPerspective() ? SkMatrix::I() : viewMatrix;
 
-        sk_sp<GrDrawOp> coverOp(GrRectOpFactory::MakeNonAAFill(args.fPaint->getColor(), viewM,
-                                                               bounds, nullptr, &invert));
+        std::unique_ptr<GrDrawOp> coverOp(GrRectOpFactory::MakeNonAAFill(
+                args.fPaint.getColor(), viewM, bounds, nullptr, &invert));
 
         // fake inverse with a stencil and cover
         args.fRenderTargetContext->priv().stencilPath(*args.fClip, args.fAAType, viewMatrix,
@@ -138,7 +138,7 @@
             if (GrAAType::kMixedSamples == coverAAType) {
                 coverAAType = GrAAType::kNone;
             }
-            GrPipelineBuilder pipelineBuilder(*args.fPaint, coverAAType);
+            GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), coverAAType);
             pipelineBuilder.setUserStencil(&kInvertedCoverPass);
 
             args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(coverOp));
@@ -154,9 +154,10 @@
                 0xffff>()
         );
 
-        sk_sp<GrDrawOp> op = GrDrawPathOp::Make(viewMatrix, args.fPaint->getColor(), path.get());
+        std::unique_ptr<GrDrawOp> op =
+                GrDrawPathOp::Make(viewMatrix, args.fPaint.getColor(), path.get());
 
-        GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fAAType);
+        GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
         pipelineBuilder.setUserStencil(&kCoverPass);
         args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(op));
     }
diff --git a/src/gpu/ops/GrStencilPathOp.h b/src/gpu/ops/GrStencilPathOp.h
index fc29402..f21b524 100644
--- a/src/gpu/ops/GrStencilPathOp.h
+++ b/src/gpu/ops/GrStencilPathOp.h
@@ -19,25 +19,21 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrOp> Make(const SkMatrix& viewMatrix,
-                            bool useHWAA,
-                            GrPathRendering::FillType fillType,
-                            bool hasStencilClip,
-                            int numStencilBits,
-                            const GrScissorState& scissor,
-                            GrRenderTarget* renderTarget,
-                            const GrPath* path) {
-        return sk_sp<GrOp>(new GrStencilPathOp(viewMatrix, useHWAA, fillType, hasStencilClip,
-                                               numStencilBits, scissor, renderTarget, path));
+    static std::unique_ptr<GrOp> Make(const SkMatrix& viewMatrix,
+                                      bool useHWAA,
+                                      GrPathRendering::FillType fillType,
+                                      bool hasStencilClip,
+                                      int numStencilBits,
+                                      const GrScissorState& scissor,
+                                      GrRenderTarget* renderTarget,
+                                      const GrPath* path) {
+        return std::unique_ptr<GrOp>(new GrStencilPathOp(viewMatrix, useHWAA, fillType,
+                                                         hasStencilClip, numStencilBits, scissor,
+                                                         renderTarget, path));
     }
 
     const char* name() const override { return "StencilPathOp"; }
 
-    // TODO: this needs to be updated to return GrSurfaceProxy::UniqueID
-    GrGpuResource::UniqueID renderTargetUniqueID() const override {
-        return fRenderTarget.get()->uniqueID();
-    }
-
     SkString dumpInfo() const override {
         SkString string;
         string.printf("PATH: 0x%p, AA:%d", fPath.get(), fUseHWAA);
diff --git a/src/gpu/ops/GrTessellatingPathRenderer.cpp b/src/gpu/ops/GrTessellatingPathRenderer.cpp
index 7774798..bd474a2 100644
--- a/src/gpu/ops/GrTessellatingPathRenderer.cpp
+++ b/src/gpu/ops/GrTessellatingPathRenderer.cpp
@@ -160,12 +160,12 @@
 public:
     DEFINE_OP_CLASS_ID
 
-    static sk_sp<GrDrawOp> Make(const GrColor& color,
-                                const GrShape& shape,
-                                const SkMatrix& viewMatrix,
-                                SkIRect devClipBounds,
-                                bool antiAlias) {
-        return sk_sp<GrDrawOp>(
+    static std::unique_ptr<GrDrawOp> Make(const GrColor& color,
+                                          const GrShape& shape,
+                                          const SkMatrix& viewMatrix,
+                                          SkIRect devClipBounds,
+                                          bool antiAlias) {
+        return std::unique_ptr<GrDrawOp>(
                 new TessellatingPathOp(color, shape, viewMatrix, devClipBounds, antiAlias));
     }
 
@@ -186,11 +186,9 @@
     }
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
-        if (!optimizations.readsColor()) {
-            fColor = GrColor_ILLEGAL;
-        }
         optimizations.getOverrideColorIfSet(&fColor);
-        fOptimizations = optimizations;
+        fCanTweakAlphaForCoverage = optimizations.canTweakAlphaForCoverage();
+        fNeedsLocalCoords = optimizations.readsLocalCoords();
     }
 
     SkPath getPath() const {
@@ -262,9 +260,8 @@
         SkScalar tol = GrPathUtils::kDefaultTolerance;
         bool isLinear;
         DynamicVertexAllocator allocator(gp->getVertexStride(), target);
-        bool canTweakAlphaForCoverage = fOptimizations.canTweakAlphaForCoverage();
         int count = GrTessellator::PathToTriangles(path, tol, clipBounds, &allocator,
-                                                   true, fColor, canTweakAlphaForCoverage,
+                                                   true, fColor, fCanTweakAlphaForCoverage,
                                                    &isLinear);
         if (count == 0) {
             return;
@@ -278,28 +275,26 @@
             using namespace GrDefaultGeoProcFactory;
 
             Color color(fColor);
-            LocalCoords localCoords(fOptimizations.readsLocalCoords()
-                                            ? LocalCoords::kUsePosition_Type
-                                            : LocalCoords::kUnused_Type);
+            LocalCoords::Type localCoordsType = fNeedsLocalCoords
+                                                        ? LocalCoords::kUsePosition_Type
+                                                        : LocalCoords::kUnused_Type;
             Coverage::Type coverageType;
             if (fAntiAlias) {
                 color = Color(Color::kAttribute_Type);
-                if (fOptimizations.canTweakAlphaForCoverage()) {
+                if (fCanTweakAlphaForCoverage) {
                     coverageType = Coverage::kSolid_Type;
                 } else {
                     coverageType = Coverage::kAttribute_Type;
                 }
-            } else if (fOptimizations.readsCoverage()) {
+            } else {
                 coverageType = Coverage::kSolid_Type;
-            } else {
-                coverageType = Coverage::kNone_Type;
             }
-            Coverage coverage(coverageType);
             if (fAntiAlias) {
-                gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(color, coverage, localCoords,
-                                                                 fViewMatrix);
+                gp = GrDefaultGeoProcFactory::MakeForDeviceSpace(color, coverageType,
+                                                                 localCoordsType, fViewMatrix);
             } else {
-                gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, fViewMatrix);
+                gp = GrDefaultGeoProcFactory::Make(color, coverageType, localCoordsType,
+                                                   fViewMatrix);
             }
         }
         if (fAntiAlias) {
@@ -346,7 +341,8 @@
     SkMatrix                fViewMatrix;
     SkIRect                 fDevClipBounds;
     bool                    fAntiAlias;
-    GrPipelineOptimizations fOptimizations;
+    bool                    fCanTweakAlphaForCoverage;
+    bool                    fNeedsLocalCoords;
 
     typedef GrMeshDrawOp INHERITED;
 };
@@ -358,12 +354,12 @@
     args.fClip->getConservativeBounds(args.fRenderTargetContext->width(),
                                       args.fRenderTargetContext->height(),
                                       &clipBoundsI);
-    sk_sp<GrDrawOp> op = TessellatingPathOp::Make(args.fPaint->getColor(),
-                                                  *args.fShape,
-                                                  *args.fViewMatrix,
-                                                  clipBoundsI,
-                                                  GrAAType::kCoverage == args.fAAType);
-    GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fAAType);
+    std::unique_ptr<GrDrawOp> op = TessellatingPathOp::Make(args.fPaint.getColor(),
+                                                            *args.fShape,
+                                                            *args.fViewMatrix,
+                                                            clipBoundsI,
+                                                            GrAAType::kCoverage == args.fAAType);
+    GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
     pipelineBuilder.setUserStencil(args.fUserStencilSettings);
     args.fRenderTargetContext->addDrawOp(pipelineBuilder, *args.fClip, std::move(op));
     return true;
diff --git a/src/gpu/ops/GrTestMeshDrawOp.h b/src/gpu/ops/GrTestMeshDrawOp.h
index 7fc44e1..b8143a5 100644
--- a/src/gpu/ops/GrTestMeshDrawOp.h
+++ b/src/gpu/ops/GrTestMeshDrawOp.h
@@ -30,13 +30,7 @@
 
     GrColor color() const { return fColor; }
 
-    struct Optimizations {
-        bool fColorIgnored = false;
-        bool fUsesLocalCoords = false;
-        bool fCoverageIgnored = false;
-    };
-
-    const Optimizations optimizations() const { return fOptimizations; }
+    bool usesLocalCoords() const { return fUsesLocalCoords; }
 
 private:
     void getPipelineAnalysisInput(GrPipelineAnalysisDrawOpInput* input) const override {
@@ -46,16 +40,13 @@
 
     void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
         optimizations.getOverrideColorIfSet(&fColor);
-
-        fOptimizations.fColorIgnored = !optimizations.readsColor();
-        fOptimizations.fUsesLocalCoords = optimizations.readsLocalCoords();
-        fOptimizations.fCoverageIgnored = !optimizations.readsCoverage();
+        fUsesLocalCoords = optimizations.readsLocalCoords();
     }
 
     bool onCombineIfPossible(GrOp*, const GrCaps&) override { return false; }
 
-    GrColor       fColor;
-    Optimizations fOptimizations;
+    GrColor fColor;
+    bool fUsesLocalCoords = false;
 
     typedef GrMeshDrawOp INHERITED;
 };
diff --git a/src/gpu/text/GrAtlasGlyphCache.cpp b/src/gpu/text/GrAtlasGlyphCache.cpp
index 803dbb4..e51f6cc 100644
--- a/src/gpu/text/GrAtlasGlyphCache.cpp
+++ b/src/gpu/text/GrAtlasGlyphCache.cpp
@@ -11,9 +11,11 @@
 #include "GrRectanizer.h"
 #include "GrResourceProvider.h"
 #include "GrSurfacePriv.h"
+#include "SkAutoMalloc.h"
 #include "SkString.h"
 
 #include "SkDistanceFieldGen.h"
+#include "GrDistanceFieldGenFromVector.h"
 
 bool GrAtlasGlyphCache::initAtlas(GrMaskFormat format) {
     int index = MaskFormatToAtlasIndex(format);
@@ -320,29 +322,57 @@
                                       int width, int height, void* dst) {
     SkASSERT(glyph.fWidth + 2*SK_DistanceFieldPad == width);
     SkASSERT(glyph.fHeight + 2*SK_DistanceFieldPad == height);
-    const void* image = cache->findImage(glyph);
-    if (nullptr == image) {
-        return false;
-    }
-    // now generate the distance field
-    SkASSERT(dst);
-    SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
-    if (SkMask::kA8_Format == maskFormat) {
-        // make the distance field from the image
-        SkGenerateDistanceFieldFromA8Image((unsigned char*)dst,
-                                           (unsigned char*)image,
-                                           glyph.fWidth, glyph.fHeight,
-                                           glyph.rowBytes());
-    } else if (SkMask::kBW_Format == maskFormat) {
-        // make the distance field from the image
-        SkGenerateDistanceFieldFromBWImage((unsigned char*)dst,
-                                           (unsigned char*)image,
-                                           glyph.fWidth, glyph.fHeight,
-                                           glyph.rowBytes());
-    } else {
+
+#ifndef SK_USE_LEGACY_DISTANCE_FIELDS
+    const SkPath* path = cache->findPath(glyph);
+    if (nullptr == path) {
         return false;
     }
 
+    SkDEBUGCODE(SkRect glyphBounds = SkRect::MakeXYWH(glyph.fLeft,
+                                                      glyph.fTop,
+                                                      glyph.fWidth,
+                                                      glyph.fHeight));
+    SkASSERT(glyphBounds.contains(path->getBounds()));
+
+    // now generate the distance field
+    SkASSERT(dst);
+    SkMatrix drawMatrix;
+    drawMatrix.setTranslate((SkScalar)-glyph.fLeft, (SkScalar)-glyph.fTop);
+
+    // Generate signed distance field directly from SkPath
+    bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dst,
+                                           *path, drawMatrix,
+                                           width, height, width * sizeof(unsigned char));
+
+    if (!succeed) {
+#endif
+        const void* image = cache->findImage(glyph);
+        if (nullptr == image) {
+            return false;
+        }
+
+        // now generate the distance field
+        SkASSERT(dst);
+        SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
+        if (SkMask::kA8_Format == maskFormat) {
+            // make the distance field from the image
+            SkGenerateDistanceFieldFromA8Image((unsigned char*)dst,
+                                               (unsigned char*)image,
+                                               glyph.fWidth, glyph.fHeight,
+                                               glyph.rowBytes());
+        } else if (SkMask::kBW_Format == maskFormat) {
+            // make the distance field from the image
+            SkGenerateDistanceFieldFromBWImage((unsigned char*)dst,
+                                               (unsigned char*)image,
+                                               glyph.fWidth, glyph.fHeight,
+                                               glyph.rowBytes());
+        } else {
+            return false;
+        }
+#ifndef SK_USE_LEGACY_DISTANCE_FIELDS
+    }
+#endif
     return true;
 }
 
diff --git a/src/gpu/text/GrAtlasTextBlob.cpp b/src/gpu/text/GrAtlasTextBlob.cpp
index 9aca37a..5e3b4bf 100644
--- a/src/gpu/text/GrAtlasTextBlob.cpp
+++ b/src/gpu/text/GrAtlasTextBlob.cpp
@@ -254,7 +254,7 @@
     return false;
 }
 
-inline sk_sp<GrDrawOp> GrAtlasTextBlob::makeOp(
+inline std::unique_ptr<GrDrawOp> GrAtlasTextBlob::makeOp(
         const Run::SubRunInfo& info, int glyphCount, int run, int subRun,
         const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color, const SkPaint& skPaint,
         const SkSurfaceProps& props, const GrDistanceFieldAdjustTable* distanceAdjustTable,
@@ -268,7 +268,7 @@
         subRunColor = color;
     }
 
-    sk_sp<GrAtlasTextOp> op;
+    std::unique_ptr<GrAtlasTextOp> op;
     if (info.drawAsDistanceFields()) {
         SkColor filteredColor;
         SkColorFilter* colorFilter = skPaint.getColorFilter();
@@ -297,14 +297,14 @@
     return std::move(op);
 }
 
-inline
-void GrAtlasTextBlob::flushRun(GrRenderTargetContext* rtc, const GrPaint& grPaint,
-                               const GrClip& clip, int run, const SkMatrix& viewMatrix, SkScalar x,
-                               SkScalar y,
-                               const SkPaint& skPaint, const SkSurfaceProps& props,
-                               const GrDistanceFieldAdjustTable* distanceAdjustTable,
-                               GrAtlasGlyphCache* cache) {
-    for (int subRun = 0; subRun < fRuns[run].fSubRunInfo.count(); subRun++) {
+inline void GrAtlasTextBlob::flushRun(GrRenderTargetContext* rtc, GrPaint&& grPaint,
+                                      const GrClip& clip, int run, const SkMatrix& viewMatrix,
+                                      SkScalar x, SkScalar y, const SkPaint& skPaint,
+                                      const SkSurfaceProps& props,
+                                      const GrDistanceFieldAdjustTable* distanceAdjustTable,
+                                      GrAtlasGlyphCache* cache) {
+    int lastRun = fRuns[run].fSubRunInfo.count() - 1;
+    for (int subRun = 0; subRun <= lastRun; subRun++) {
         const Run::SubRunInfo& info = fRuns[run].fSubRunInfo[subRun];
         int glyphCount = info.glyphCount();
         if (0 == glyphCount) {
@@ -313,10 +313,11 @@
 
         GrColor color = grPaint.getColor();
 
-        sk_sp<GrDrawOp> op(this->makeOp(info, glyphCount, run, subRun, viewMatrix, x, y, color,
-                                        skPaint, props, distanceAdjustTable, rtc->isGammaCorrect(),
-                                        cache));
-        GrPipelineBuilder pipelineBuilder(grPaint, GrAAType::kNone);
+        std::unique_ptr<GrDrawOp> op(this->makeOp(info, glyphCount, run, subRun, viewMatrix, x, y,
+                                                  color, skPaint, props, distanceAdjustTable,
+                                                  rtc->isGammaCorrect(), cache));
+        GrPipelineBuilder pipelineBuilder(GrPaint::MoveOrClone(grPaint, subRun < lastRun),
+                                          GrAAType::kNone);
 
         rtc->addDrawOp(pipelineBuilder, clip, std::move(op));
     }
@@ -403,18 +404,13 @@
     }
 }
 
-void GrAtlasTextBlob::flushCached(GrContext* context,
-                                  GrRenderTargetContext* rtc,
-                                  const SkTextBlob* blob,
-                                  const SkSurfaceProps& props,
+void GrAtlasTextBlob::flushCached(GrContext* context, GrRenderTargetContext* rtc,
+                                  const SkTextBlob* blob, const SkSurfaceProps& props,
                                   const GrDistanceFieldAdjustTable* distanceAdjustTable,
-                                  const SkPaint& skPaint,
-                                  const GrPaint& grPaint,
-                                  SkDrawFilter* drawFilter,
-                                  const GrClip& clip,
-                                  const SkMatrix& viewMatrix,
-                                  const SkIRect& clipBounds,
-                                  SkScalar x, SkScalar y) {
+                                  const SkPaint& skPaint, GrPaint&& grPaint,
+                                  SkDrawFilter* drawFilter, const GrClip& clip,
+                                  const SkMatrix& viewMatrix, const SkIRect& clipBounds, SkScalar x,
+                                  SkScalar y) {
     // We loop through the runs of the blob, flushing each.  If any run is too large, then we flush
     // it as paths
     SkTextBlobRunIterator it(blob);
@@ -424,39 +420,34 @@
                                   drawFilter, viewMatrix, clipBounds, x, y);
             continue;
         }
-        this->flushRun(rtc, grPaint, clip, run, viewMatrix, x, y, skPaint, props,
-                       distanceAdjustTable, context->getAtlasGlyphCache());
+        this->flushRun(rtc, GrPaint::MoveOrClone(grPaint, !it.done()), clip, run, viewMatrix, x, y,
+                       skPaint, props, distanceAdjustTable, context->getAtlasGlyphCache());
     }
 
     // Now flush big glyphs
     this->flushBigGlyphs(context, rtc, clip, skPaint, viewMatrix, x, y, clipBounds);
 }
 
-void GrAtlasTextBlob::flushThrowaway(GrContext* context,
-                                     GrRenderTargetContext* rtc,
+void GrAtlasTextBlob::flushThrowaway(GrContext* context, GrRenderTargetContext* rtc,
                                      const SkSurfaceProps& props,
                                      const GrDistanceFieldAdjustTable* distanceAdjustTable,
-                                     const SkPaint& skPaint,
-                                     const GrPaint& grPaint,
-                                     const GrClip& clip,
-                                     const SkMatrix& viewMatrix,
-                                     const SkIRect& clipBounds,
+                                     const SkPaint& skPaint, GrPaint&& grPaint, const GrClip& clip,
+                                     const SkMatrix& viewMatrix, const SkIRect& clipBounds,
                                      SkScalar x, SkScalar y) {
     for (int run = 0; run < fRunCount; run++) {
-        this->flushRun(rtc, grPaint, clip, run, viewMatrix, x, y, skPaint, props,
-                       distanceAdjustTable, context->getAtlasGlyphCache());
+        this->flushRun(rtc, GrPaint::MoveOrClone(grPaint, run + 1 != fRunCount), clip, run,
+                       viewMatrix, x, y, skPaint, props, distanceAdjustTable,
+                       context->getAtlasGlyphCache());
     }
 
     // Now flush big glyphs
     this->flushBigGlyphs(context, rtc, clip, skPaint, viewMatrix, x, y, clipBounds);
 }
 
-sk_sp<GrDrawOp> GrAtlasTextBlob::test_makeOp(int glyphCount, int run, int subRun,
-                                             const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
-                                             GrColor color, const SkPaint& skPaint,
-                                             const SkSurfaceProps& props,
-                                             const GrDistanceFieldAdjustTable* distanceAdjustTable,
-                                             GrAtlasGlyphCache* cache) {
+std::unique_ptr<GrDrawOp> GrAtlasTextBlob::test_makeOp(
+        int glyphCount, int run, int subRun, const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
+        GrColor color, const SkPaint& skPaint, const SkSurfaceProps& props,
+        const GrDistanceFieldAdjustTable* distanceAdjustTable, GrAtlasGlyphCache* cache) {
     const GrAtlasTextBlob::Run::SubRunInfo& info = fRuns[run].fSubRunInfo[subRun];
     return this->makeOp(info, glyphCount, run, subRun, viewMatrix, x, y, color, skPaint, props,
                         distanceAdjustTable, false, cache);
diff --git a/src/gpu/text/GrAtlasTextBlob.h b/src/gpu/text/GrAtlasTextBlob.h
index 9b3f59c..f4e149f 100644
--- a/src/gpu/text/GrAtlasTextBlob.h
+++ b/src/gpu/text/GrAtlasTextBlob.h
@@ -181,30 +181,18 @@
                         const SkMatrix& viewMatrix, SkScalar x, SkScalar y);
 
     // flush a GrAtlasTextBlob associated with a SkTextBlob
-    void flushCached(GrContext* context,
-                     GrRenderTargetContext* rtc,
-                     const SkTextBlob* blob,
+    void flushCached(GrContext* context, GrRenderTargetContext* rtc, const SkTextBlob* blob,
                      const SkSurfaceProps& props,
-                     const GrDistanceFieldAdjustTable* distanceAdjustTable,
-                     const SkPaint& skPaint,
-                     const GrPaint& grPaint,
-                     SkDrawFilter* drawFilter,
-                     const GrClip& clip,
-                     const SkMatrix& viewMatrix,
-                     const SkIRect& clipBounds,
-                     SkScalar x, SkScalar y);
+                     const GrDistanceFieldAdjustTable* distanceAdjustTable, const SkPaint& skPaint,
+                     GrPaint&& grPaint, SkDrawFilter* drawFilter, const GrClip& clip,
+                     const SkMatrix& viewMatrix, const SkIRect& clipBounds, SkScalar x, SkScalar y);
 
     // flush a throwaway GrAtlasTextBlob *not* associated with an SkTextBlob
-    void flushThrowaway(GrContext* context,
-                        GrRenderTargetContext* rtc,
-                        const SkSurfaceProps& props,
+    void flushThrowaway(GrContext* context, GrRenderTargetContext* rtc, const SkSurfaceProps& props,
                         const GrDistanceFieldAdjustTable* distanceAdjustTable,
-                        const SkPaint& skPaint,
-                        const GrPaint& grPaint,
-                        const GrClip& clip,
-                        const SkMatrix& viewMatrix,
-                        const SkIRect& clipBounds,
-                        SkScalar x, SkScalar y);
+                        const SkPaint& skPaint, GrPaint&& grPaint, const GrClip& clip,
+                        const SkMatrix& viewMatrix, const SkIRect& clipBounds, SkScalar x,
+                        SkScalar y);
 
     void computeSubRunBounds(SkRect* outBounds, int runIndex, int subRunIndex,
                              const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
@@ -279,11 +267,12 @@
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
     // Internal test methods
-    sk_sp<GrDrawOp> test_makeOp(int glyphCount, int run, int subRun, const SkMatrix& viewMatrix,
-                                SkScalar x, SkScalar y, GrColor color, const SkPaint& skPaint,
-                                const SkSurfaceProps& props,
-                                const GrDistanceFieldAdjustTable* distanceAdjustTable,
-                                GrAtlasGlyphCache* cache);
+    std::unique_ptr<GrDrawOp> test_makeOp(int glyphCount, int run, int subRun,
+                                          const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
+                                          GrColor color, const SkPaint& skPaint,
+                                          const SkSurfaceProps& props,
+                                          const GrDistanceFieldAdjustTable* distanceAdjustTable,
+                                          GrAtlasGlyphCache* cache);
 
 private:
     GrAtlasTextBlob()
@@ -294,9 +283,9 @@
     void appendLargeGlyph(GrGlyph* glyph, SkGlyphCache* cache, const SkGlyph& skGlyph,
                           SkScalar x, SkScalar y, SkScalar scale, bool treatAsBMP);
 
-    inline void flushRun(GrRenderTargetContext* rtc, const GrPaint&, const GrClip&,
-                         int run, const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
-                         const SkPaint& skPaint, const SkSurfaceProps& props,
+    inline void flushRun(GrRenderTargetContext* rtc, GrPaint&&, const GrClip&, int run,
+                         const SkMatrix& viewMatrix, SkScalar x, SkScalar y, const SkPaint& skPaint,
+                         const SkSurfaceProps& props,
                          const GrDistanceFieldAdjustTable* distanceAdjustTable,
                          GrAtlasGlyphCache* cache);
 
@@ -500,11 +489,13 @@
                    Run* run, Run::SubRunInfo* info, SkAutoGlyphCache*, int glyphCount,
                    size_t vertexStride, GrColor color, SkScalar transX, SkScalar transY) const;
 
-    inline sk_sp<GrDrawOp> makeOp(const Run::SubRunInfo& info, int glyphCount, int run, int subRun,
-                                  const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color,
-                                  const SkPaint& skPaint, const SkSurfaceProps& props,
-                                  const GrDistanceFieldAdjustTable* distanceAdjustTable,
-                                  bool useGammaCorrectDistanceTable, GrAtlasGlyphCache* cache);
+    inline std::unique_ptr<GrDrawOp> makeOp(const Run::SubRunInfo& info, int glyphCount, int run,
+                                            int subRun, const SkMatrix& viewMatrix, SkScalar x,
+                                            SkScalar y, GrColor color, const SkPaint& skPaint,
+                                            const SkSurfaceProps& props,
+                                            const GrDistanceFieldAdjustTable* distanceAdjustTable,
+                                            bool useGammaCorrectDistanceTable,
+                                            GrAtlasGlyphCache* cache);
 
     struct BigGlyph {
         BigGlyph(const SkPath& path, SkScalar vx, SkScalar vy, SkScalar scale, bool treatAsBMP)
diff --git a/src/gpu/text/GrAtlasTextContext.cpp b/src/gpu/text/GrAtlasTextContext.cpp
index 3c155e3..1d8571c 100644
--- a/src/gpu/text/GrAtlasTextContext.cpp
+++ b/src/gpu/text/GrAtlasTextContext.cpp
@@ -168,7 +168,7 @@
     }
 
     cacheBlob->flushCached(context, rtc, blob, props, fDistanceAdjustTable.get(), skPaint,
-                           grPaint, drawFilter, clip, viewMatrix, clipBounds, x, y);
+                           std::move(grPaint), drawFilter, clip, viewMatrix, clipBounds, x, y);
 }
 
 void GrAtlasTextContext::RegenerateTextBlob(GrAtlasTextBlob* cacheBlob,
@@ -312,14 +312,11 @@
     return blob;
 }
 
-void GrAtlasTextContext::drawText(GrContext* context,
-                                  GrRenderTargetContext* rtc,
-                                  const GrClip& clip,
-                                  const GrPaint& paint, const SkPaint& skPaint,
-                                  const SkMatrix& viewMatrix,
-                                  const SkSurfaceProps& props,
-                                  const char text[], size_t byteLength,
-                                  SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
+void GrAtlasTextContext::drawText(GrContext* context, GrRenderTargetContext* rtc,
+                                  const GrClip& clip, GrPaint&& paint, const SkPaint& skPaint,
+                                  const SkMatrix& viewMatrix, const SkSurfaceProps& props,
+                                  const char text[], size_t byteLength, SkScalar x, SkScalar y,
+                                  const SkIRect& regionClipBounds) {
     if (context->abandoned()) {
         return;
     } else if (this->canDraw(skPaint, viewMatrix, props, *context->caps()->shaderCaps())) {
@@ -330,8 +327,8 @@
                                ComputeScalerContextFlags(rtc),
                                viewMatrix, props,
                                text, byteLength, x, y));
-        blob->flushThrowaway(context, rtc, props, fDistanceAdjustTable.get(), skPaint, paint,
-                             clip, viewMatrix, regionClipBounds, x, y);
+        blob->flushThrowaway(context, rtc, props, fDistanceAdjustTable.get(), skPaint,
+                             std::move(paint), clip, viewMatrix, regionClipBounds, x, y);
         return;
     }
 
@@ -340,15 +337,12 @@
                                 regionClipBounds);
 }
 
-void GrAtlasTextContext::drawPosText(GrContext* context,
-                                     GrRenderTargetContext* rtc,
-                                     const GrClip& clip,
-                                     const GrPaint& paint, const SkPaint& skPaint,
-                                     const SkMatrix& viewMatrix,
-                                     const SkSurfaceProps& props,
-                                     const char text[], size_t byteLength,
-                                     const SkScalar pos[], int scalarsPerPosition,
-                                     const SkPoint& offset, const SkIRect& regionClipBounds) {
+void GrAtlasTextContext::drawPosText(GrContext* context, GrRenderTargetContext* rtc,
+                                     const GrClip& clip, GrPaint&& paint, const SkPaint& skPaint,
+                                     const SkMatrix& viewMatrix, const SkSurfaceProps& props,
+                                     const char text[], size_t byteLength, const SkScalar pos[],
+                                     int scalarsPerPosition, const SkPoint& offset,
+                                     const SkIRect& regionClipBounds) {
     if (context->abandoned()) {
         return;
     } else if (this->canDraw(skPaint, viewMatrix, props, *context->caps()->shaderCaps())) {
@@ -362,8 +356,9 @@
                                   text, byteLength,
                                   pos, scalarsPerPosition,
                                   offset));
-        blob->flushThrowaway(context, rtc, props, fDistanceAdjustTable.get(), skPaint, paint,
-                             clip, viewMatrix, regionClipBounds, offset.fX, offset.fY);
+        blob->flushThrowaway(context, rtc, props, fDistanceAdjustTable.get(), skPaint,
+                             std::move(paint), clip, viewMatrix, regionClipBounds, offset.fX,
+                             offset.fY);
         return;
     }
 
diff --git a/src/gpu/text/GrAtlasTextContext.h b/src/gpu/text/GrAtlasTextContext.h
index 0417935..27560d4 100644
--- a/src/gpu/text/GrAtlasTextContext.h
+++ b/src/gpu/text/GrAtlasTextContext.h
@@ -32,14 +32,12 @@
 
     bool canDraw(const SkPaint&, const SkMatrix& viewMatrix, const SkSurfaceProps&,
                  const GrShaderCaps&);
-    void drawText(GrContext*, GrRenderTargetContext*, const GrClip&, const GrPaint&, const SkPaint&,
+    void drawText(GrContext*, GrRenderTargetContext*, const GrClip&, GrPaint&&, const SkPaint&,
                   const SkMatrix& viewMatrix, const SkSurfaceProps&, const char text[],
-                  size_t byteLength, SkScalar x, SkScalar y,
-                  const SkIRect& regionClipBounds);
-    void drawPosText(GrContext*, GrRenderTargetContext*, const GrClip&, const GrPaint&,
-                     const SkPaint&, const SkMatrix& viewMatrix, const SkSurfaceProps&,
-                     const char text[], size_t byteLength,
-                     const SkScalar pos[], int scalarsPerPosition,
+                  size_t byteLength, SkScalar x, SkScalar y, const SkIRect& regionClipBounds);
+    void drawPosText(GrContext*, GrRenderTargetContext*, const GrClip&, GrPaint&&, const SkPaint&,
+                     const SkMatrix& viewMatrix, const SkSurfaceProps&, const char text[],
+                     size_t byteLength, const SkScalar pos[], int scalarsPerPosition,
                      const SkPoint& offset, const SkIRect& regionClipBounds);
     void drawTextBlob(GrContext*, GrRenderTargetContext*, const GrClip&, const SkPaint&,
                       const SkMatrix& viewMatrix, const SkSurfaceProps&, const SkTextBlob*,
diff --git a/src/gpu/text/GrStencilAndCoverTextContext.cpp b/src/gpu/text/GrStencilAndCoverTextContext.cpp
index e3ad1a7..7440223 100644
--- a/src/gpu/text/GrStencilAndCoverTextContext.cpp
+++ b/src/gpu/text/GrStencilAndCoverTextContext.cpp
@@ -69,25 +69,25 @@
 }
 
 void GrStencilAndCoverTextContext::drawText(GrContext* context, GrRenderTargetContext* rtc,
-                                            const GrClip& clip, const GrPaint& paint,
+                                            const GrClip& clip, GrPaint&& paint,
                                             const SkPaint& skPaint, const SkMatrix& viewMatrix,
-                                            const SkSurfaceProps& props,
-                                            const char text[], size_t byteLength,
-                                            SkScalar x, SkScalar y, const SkIRect& clipBounds) {
+                                            const SkSurfaceProps& props, const char text[],
+                                            size_t byteLength, SkScalar x, SkScalar y,
+                                            const SkIRect& clipBounds) {
     if (context->abandoned()) {
         return;
     } else if (this->canDraw(skPaint, viewMatrix)) {
         if (skPaint.getTextSize() > 0) {
             TextRun run(skPaint);
             run.setText(text, byteLength, x, y);
-            run.draw(context, rtc, paint, clip, viewMatrix, props, 0, 0,
-                     clipBounds, fFallbackTextContext, skPaint);
+            run.draw(context, rtc, std::move(paint), clip, viewMatrix, props, 0, 0, clipBounds,
+                     fFallbackTextContext, skPaint);
         }
         return;
     } else if (fFallbackTextContext->canDraw(skPaint, viewMatrix, props,
                                              *context->caps()->shaderCaps())) {
-        fFallbackTextContext->drawText(context, rtc, clip, paint, skPaint, viewMatrix, props, text,
-                                       byteLength, x, y, clipBounds);
+        fFallbackTextContext->drawText(context, rtc, clip, std::move(paint), skPaint, viewMatrix,
+                                       props, text, byteLength, x, y, clipBounds);
         return;
     }
 
@@ -97,16 +97,11 @@
 }
 
 void GrStencilAndCoverTextContext::drawPosText(GrContext* context, GrRenderTargetContext* rtc,
-                                               const GrClip& clip,
-                                               const GrPaint& paint,
-                                               const SkPaint& skPaint,
-                                               const SkMatrix& viewMatrix,
-                                               const SkSurfaceProps& props,
-                                               const char text[],
-                                               size_t byteLength,
-                                               const SkScalar pos[],
-                                               int scalarsPerPosition,
-                                               const SkPoint& offset,
+                                               const GrClip& clip, GrPaint&& paint,
+                                               const SkPaint& skPaint, const SkMatrix& viewMatrix,
+                                               const SkSurfaceProps& props, const char text[],
+                                               size_t byteLength, const SkScalar pos[],
+                                               int scalarsPerPosition, const SkPoint& offset,
                                                const SkIRect& clipBounds) {
     if (context->abandoned()) {
         return;
@@ -114,15 +109,15 @@
         if (skPaint.getTextSize() > 0) {
             TextRun run(skPaint);
             run.setPosText(text, byteLength, pos, scalarsPerPosition, offset);
-            run.draw(context, rtc, paint, clip, viewMatrix, props, 0, 0,
-                     clipBounds, fFallbackTextContext, skPaint);
+            run.draw(context, rtc, std::move(paint), clip, viewMatrix, props, 0, 0, clipBounds,
+                     fFallbackTextContext, skPaint);
         }
         return;
     } else if (fFallbackTextContext->canDraw(skPaint, viewMatrix, props,
                                              *context->caps()->shaderCaps())) {
-        fFallbackTextContext->drawPosText(context, rtc, clip, paint, skPaint, viewMatrix, props,
-                                          text, byteLength, pos,
-                                          scalarsPerPosition, offset, clipBounds);
+        fFallbackTextContext->drawPosText(context, rtc, clip, std::move(paint), skPaint, viewMatrix,
+                                          props, text, byteLength, pos, scalarsPerPosition, offset,
+                                          clipBounds);
         return;
     }
 
@@ -167,20 +162,19 @@
 
         switch (it.positioning()) {
             case SkTextBlob::kDefault_Positioning:
-                this->drawText(context, rtc, clip, grPaint, runPaint, viewMatrix, props,
-                               (const char *)it.glyphs(),
-                               textLen, x + offset.x(), y + offset.y(), clipBounds);
+                this->drawText(context, rtc, clip, std::move(grPaint), runPaint, viewMatrix, props,
+                               (const char*)it.glyphs(), textLen, x + offset.x(), y + offset.y(),
+                               clipBounds);
                 break;
             case SkTextBlob::kHorizontal_Positioning:
-                this->drawPosText(context, rtc, clip, grPaint, runPaint, viewMatrix, props,
-                                  (const char*)it.glyphs(),
-                                  textLen, it.pos(), 1, SkPoint::Make(x, y + offset.y()),
-                                  clipBounds);
+                this->drawPosText(context, rtc, clip, std::move(grPaint), runPaint, viewMatrix,
+                                  props, (const char*)it.glyphs(), textLen, it.pos(), 1,
+                                  SkPoint::Make(x, y + offset.y()), clipBounds);
                 break;
             case SkTextBlob::kFull_Positioning:
-                this->drawPosText(context, rtc, clip, grPaint, runPaint, viewMatrix, props,
-                                  (const char*)it.glyphs(),
-                                  textLen, it.pos(), 2, SkPoint::Make(x, y), clipBounds);
+                this->drawPosText(context, rtc, clip, std::move(grPaint), runPaint, viewMatrix,
+                                  props, (const char*)it.glyphs(), textLen, it.pos(), 2,
+                                  SkPoint::Make(x, y), clipBounds);
                 break;
         }
 
@@ -223,9 +217,10 @@
     const TextBlob& blob = this->findOrCreateTextBlob(skBlob, skPaint);
 
     TextBlob::Iter iter(blob);
-    for (TextRun* run = iter.get(); run; run = iter.next()) {
-        run->draw(context, rtc, paint, clip, viewMatrix, props,  x, y, clipBounds,
-                  fFallbackTextContext, skPaint);
+    for (TextRun *run = iter.get(), *nextRun; run; run = nextRun) {
+        nextRun = iter.next();
+        run->draw(context, rtc, GrPaint::MoveOrClone(paint, nextRun), clip, viewMatrix, props, x, y,
+                  clipBounds, fFallbackTextContext, skPaint);
         run->releaseGlyphCache();
     }
 }
@@ -593,16 +588,11 @@
     }
 }
 
-void GrStencilAndCoverTextContext::TextRun::draw(GrContext* ctx,
-                                                 GrRenderTargetContext* renderTargetContext,
-                                                 const GrPaint& grPaint,
-                                                 const GrClip& clip,
-                                                 const SkMatrix& viewMatrix,
-                                                 const SkSurfaceProps& props,
-                                                 SkScalar x, SkScalar y,
-                                                 const SkIRect& clipBounds,
-                                                 GrAtlasTextContext* fallbackTextContext,
-                                                 const SkPaint& originalSkPaint) const {
+void GrStencilAndCoverTextContext::TextRun::draw(
+        GrContext* ctx, GrRenderTargetContext* renderTargetContext, GrPaint&& grPaint,
+        const GrClip& clip, const SkMatrix& viewMatrix, const SkSurfaceProps& props, SkScalar x,
+        SkScalar y, const SkIRect& clipBounds, GrAtlasTextContext* fallbackTextContext,
+        const SkPaint& originalSkPaint) const {
     GrAA runAA = this->isAntiAlias();
     SkASSERT(fInstanceData);
     SkASSERT(renderTargetContext->isStencilBufferMultisampled() || GrAA::kNo == runAA);
@@ -633,10 +623,10 @@
         const SkRect bounds = SkRect::MakeIWH(renderTargetContext->width(),
                                               renderTargetContext->height());
 
-        sk_sp<GrDrawOp> op = GrDrawPathRangeOp::Make(viewMatrix, fTextRatio, fTextInverseRatio * x,
-                                                     fTextInverseRatio * y, grPaint.getColor(),
-                                                     GrPathRendering::kWinding_FillType,
-                                                     glyphs.get(), fInstanceData.get(), bounds);
+        std::unique_ptr<GrDrawOp> op = GrDrawPathRangeOp::Make(
+                viewMatrix, fTextRatio, fTextInverseRatio * x, fTextInverseRatio * y,
+                grPaint.getColor(), GrPathRendering::kWinding_FillType, glyphs.get(),
+                fInstanceData.get(), bounds);
 
         // The run's "font" overrides the anti-aliasing of the passed in SkPaint!
         GrAAType aaType = GrAAType::kNone;
@@ -647,7 +637,7 @@
                 aaType = GrAAType::kMixedSamples;
             }
         }
-        GrPipelineBuilder pipelineBuilder(grPaint, aaType);
+        GrPipelineBuilder pipelineBuilder(std::move(grPaint), aaType);
         pipelineBuilder.setUserStencil(&kCoverPass);
 
         renderTargetContext->addDrawOp(pipelineBuilder, clip, std::move(op));
diff --git a/src/gpu/text/GrStencilAndCoverTextContext.h b/src/gpu/text/GrStencilAndCoverTextContext.h
index 719d338..0597225 100644
--- a/src/gpu/text/GrStencilAndCoverTextContext.h
+++ b/src/gpu/text/GrStencilAndCoverTextContext.h
@@ -31,16 +31,12 @@
 public:
     static GrStencilAndCoverTextContext* Create(GrAtlasTextContext* fallbackTextContext);
 
-    void drawText(GrContext*, GrRenderTargetContext* rtc,
-                  const GrClip&,  const GrPaint&, const SkPaint&,
+    void drawText(GrContext*, GrRenderTargetContext* rtc, const GrClip&, GrPaint&&, const SkPaint&,
                   const SkMatrix& viewMatrix, const SkSurfaceProps&, const char text[],
-                  size_t byteLength, SkScalar x,
-                  SkScalar y, const SkIRect& clipBounds);
-    void drawPosText(GrContext*, GrRenderTargetContext*,
-                     const GrClip&, const GrPaint&, const SkPaint&,
-                     const SkMatrix& viewMatrix, const SkSurfaceProps&,
-                     const char text[], size_t byteLength,
-                     const SkScalar pos[], int scalarsPerPosition,
+                  size_t byteLength, SkScalar x, SkScalar y, const SkIRect& clipBounds);
+    void drawPosText(GrContext*, GrRenderTargetContext*, const GrClip&, GrPaint&&, const SkPaint&,
+                     const SkMatrix& viewMatrix, const SkSurfaceProps&, const char text[],
+                     size_t byteLength, const SkScalar pos[], int scalarsPerPosition,
                      const SkPoint& offset, const SkIRect& clipBounds);
     void drawTextBlob(GrContext*, GrRenderTargetContext*, const GrClip&, const SkPaint&,
                       const SkMatrix& viewMatrix, const SkSurfaceProps&, const SkTextBlob*,
@@ -79,9 +75,8 @@
         void setPosText(const char text[], size_t byteLength, const SkScalar pos[],
                         int scalarsPerPosition, const SkPoint& offset);
 
-        void draw(GrContext*, GrRenderTargetContext*, const GrPaint&, const GrClip&,
-                  const SkMatrix&, const SkSurfaceProps&,
-                  SkScalar x, SkScalar y, const SkIRect& clipBounds,
+        void draw(GrContext*, GrRenderTargetContext*, GrPaint&&, const GrClip&, const SkMatrix&,
+                  const SkSurfaceProps&, SkScalar x, SkScalar y, const SkIRect& clipBounds,
                   GrAtlasTextContext* fallbackTextContext, const SkPaint& originalSkPaint) const;
 
         void releaseGlyphCache() const;
diff --git a/src/gpu/vk/GrVkBackendContext.cpp b/src/gpu/vk/GrVkBackendContext.cpp
index 93ae01f..5a9de04 100644
--- a/src/gpu/vk/GrVkBackendContext.cpp
+++ b/src/gpu/vk/GrVkBackendContext.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "vk/GrVkBackendContext.h"
 #include "vk/GrVkExtensions.h"
 #include "vk/GrVkInterface.h"
diff --git a/src/gpu/vk/GrVkBuffer.cpp b/src/gpu/vk/GrVkBuffer.cpp
index 82674b4..a0faa21 100644
--- a/src/gpu/vk/GrVkBuffer.cpp
+++ b/src/gpu/vk/GrVkBuffer.cpp
@@ -110,6 +110,9 @@
     VALIDATE();
     fResource->recycle(const_cast<GrVkGpu*>(gpu));
     fResource = nullptr;
+    if (!fDesc.fDynamic) {
+        delete[] (unsigned char*)fMapPtr;
+    }
     fMapPtr = nullptr;
     VALIDATE();
 }
@@ -117,6 +120,9 @@
 void GrVkBuffer::vkAbandon() {
     fResource->unrefAndAbandon();
     fResource = nullptr;
+    if (!fDesc.fDynamic) {
+        delete[] (unsigned char*)fMapPtr;
+    }
     fMapPtr = nullptr;
     VALIDATE();
 }
diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index 0c52643..f614ae3 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -148,7 +148,9 @@
             shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRR();
             shaderCaps->fConfigOutputSwizzle[i] = GrSwizzle::AAAA();
         } else {
-            if (kRGBA_4444_GrPixelConfig == config) {
+            if (kGray_8_GrPixelConfig == config) {
+                shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRA();
+            } else if (kRGBA_4444_GrPixelConfig == config) {
                 // The vulkan spec does not require R4G4B4A4 to be supported for texturing so we
                 // store the data in a B4G4R4A4 texture and then swizzle it when doing texture reads
                 // or writing to outputs. Since we're not actually changing the data at all, the
diff --git a/src/gpu/vk/GrVkCopyManager.cpp b/src/gpu/vk/GrVkCopyManager.cpp
index 7b2c734..b1f8f2a 100644
--- a/src/gpu/vk/GrVkCopyManager.cpp
+++ b/src/gpu/vk/GrVkCopyManager.cpp
@@ -25,6 +25,13 @@
 #include "SkPoint.h"
 #include "SkRect.h"
 
+GrVkCopyManager::GrVkCopyManager()
+    : fVertShaderModule(VK_NULL_HANDLE)
+    , fFragShaderModule(VK_NULL_HANDLE)
+    , fPipelineLayout(VK_NULL_HANDLE) {}
+
+GrVkCopyManager::~GrVkCopyManager() {}
+
 bool GrVkCopyManager::createCopyProgram(GrVkGpu* gpu) {
     const GrShaderCaps* shaderCaps = gpu->caps()->shaderCaps();
     const char* version = shaderCaps->versionDeclString();
@@ -125,8 +132,8 @@
     fVertexBuffer->updateData(vdata, sizeof(vdata));
 
     // We use 2 vec4's for uniforms
-    fUniformBuffer = GrVkUniformBuffer::Create(gpu, 8 * sizeof(float));
-    SkASSERT(fUniformBuffer);
+    fUniformBuffer.reset(GrVkUniformBuffer::Create(gpu, 8 * sizeof(float)));
+    SkASSERT(fUniformBuffer.get());
 
     return true;
 }
@@ -154,7 +161,7 @@
         SkASSERT(VK_NULL_HANDLE == fFragShaderModule &&
                  VK_NULL_HANDLE == fPipelineLayout &&
                  nullptr == fVertexBuffer.get() &&
-                 nullptr == fUniformBuffer);
+                 nullptr == fUniformBuffer.get());
         if (!this->createCopyProgram(gpu)) {
             SkDebugf("Failed to create copy program.\n");
             return false;
@@ -392,7 +399,7 @@
 
     if (fUniformBuffer) {
         fUniformBuffer->release(gpu);
-        fUniformBuffer = nullptr;
+        fUniformBuffer.reset();
     }
 }
 
@@ -403,6 +410,6 @@
 
     if (fUniformBuffer) {
         fUniformBuffer->abandon();
-        fUniformBuffer = nullptr;
+        fUniformBuffer.reset();
     }
 }
diff --git a/src/gpu/vk/GrVkCopyManager.h b/src/gpu/vk/GrVkCopyManager.h
index 3a92d58..36a08be 100644
--- a/src/gpu/vk/GrVkCopyManager.h
+++ b/src/gpu/vk/GrVkCopyManager.h
@@ -22,11 +22,9 @@
 
 class GrVkCopyManager {
 public:
-    GrVkCopyManager()
-        : fVertShaderModule(VK_NULL_HANDLE)
-        , fFragShaderModule(VK_NULL_HANDLE)
-        , fPipelineLayout(VK_NULL_HANDLE)
-        , fUniformBuffer(nullptr) {}
+    GrVkCopyManager();
+
+    ~GrVkCopyManager();
 
     bool copySurfaceAsDraw(GrVkGpu* gpu,
                            GrSurface* dst,
@@ -49,7 +47,7 @@
     VkPipelineLayout fPipelineLayout;
 
     sk_sp<GrVkVertexBuffer> fVertexBuffer;
-    GrVkUniformBuffer* fUniformBuffer;
+    std::unique_ptr<GrVkUniformBuffer> fUniformBuffer;
 };
 
 #endif
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index f31c743..853e68c 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -257,7 +257,7 @@
 bool GrVkGpu::onGetWritePixelsInfo(GrSurface* dstSurface, int width, int height,
                                    GrPixelConfig srcConfig, DrawPreference* drawPreference,
                                    WritePixelTempDrawInfo* tempDrawInfo) {
-    if (kIndex_8_GrPixelConfig == srcConfig || GrPixelConfigIsCompressed(dstSurface->config())) {
+    if (GrPixelConfigIsCompressed(dstSurface->config())) {
         return false;
     }
 
diff --git a/src/gpu/vk/GrVkPipelineState.cpp b/src/gpu/vk/GrVkPipelineState.cpp
index e5bf7fa..257763a 100644
--- a/src/gpu/vk/GrVkPipelineState.cpp
+++ b/src/gpu/vk/GrVkPipelineState.cpp
@@ -75,7 +75,7 @@
 }
 
 GrVkPipelineState::~GrVkPipelineState() {
-    // Must of freed all GPU resources before this is destroyed
+    // Must have freed all GPU resources before this is destroyed
     SkASSERT(!fPipeline);
     SkASSERT(!fPipelineLayout);
     SkASSERT(!fSamplers.count());
diff --git a/src/gpu/vk/GrVkPipelineStateCache.cpp b/src/gpu/vk/GrVkPipelineStateCache.cpp
index 62ef3c2..7b935eb 100644
--- a/src/gpu/vk/GrVkPipelineStateCache.cpp
+++ b/src/gpu/vk/GrVkPipelineStateCache.cpp
@@ -22,25 +22,22 @@
 #endif
 
 struct GrVkResourceProvider::PipelineStateCache::Entry {
+    Entry(GrVkGpu* gpu, sk_sp<GrVkPipelineState> pipelineState)
+    : fGpu(gpu)
+    , fPipelineState(pipelineState) {}
 
-    Entry() : fPipelineState(nullptr) {}
-
-    static const GrVkPipelineState::Desc& GetKey(const Entry* entry) {
-        return entry->fPipelineState->getDesc();
+    ~Entry() {
+        if (fPipelineState) {
+            fPipelineState->freeGPUResources(fGpu);
+        }
     }
 
-    static uint32_t Hash(const GrVkPipelineState::Desc& key) {
-        return key.getChecksum();
-    }
-
+    GrVkGpu* fGpu;
     sk_sp<GrVkPipelineState> fPipelineState;
-
-private:
-    SK_DECLARE_INTERNAL_LLIST_INTERFACE(Entry);
 };
 
 GrVkResourceProvider::PipelineStateCache::PipelineStateCache(GrVkGpu* gpu)
-    : fCount(0)
+    : fMap(kMaxEntries)
     , fGpu(gpu)
 #ifdef GR_PIPELINE_STATE_CACHE_STATS
     , fTotalRequests(0)
@@ -49,7 +46,7 @@
 {}
 
 GrVkResourceProvider::PipelineStateCache::~PipelineStateCache() {
-    SkASSERT(0 == fCount);
+    SkASSERT(0 == fMap.count());
     // dump stats
 #ifdef GR_PIPELINE_STATE_CACHE_STATS
     if (c_DisplayVkPipelineCache) {
@@ -64,30 +61,16 @@
 #endif
 }
 
-void GrVkResourceProvider::PipelineStateCache::reset() {
-    fHashTable.foreach([](Entry** entry) {
-        delete *entry;
-    });
-    fHashTable.reset();
-    fCount = 0;
-}
-
 void GrVkResourceProvider::PipelineStateCache::abandon() {
-    fHashTable.foreach([](Entry** entry) {
-        SkASSERT((*entry)->fPipelineState.get());
-        (*entry)->fPipelineState->abandonGPUResources();
+    fMap.foreach([](std::unique_ptr<Entry>* e) {
+        (*e)->fPipelineState->abandonGPUResources();
+        (*e)->fPipelineState = nullptr;
     });
-
-    this->reset();
+    fMap.reset();
 }
 
 void GrVkResourceProvider::PipelineStateCache::release() {
-    fHashTable.foreach([this](Entry** entry) {
-        SkASSERT((*entry)->fPipelineState.get());
-        (*entry)->fPipelineState->freeGPUResources(fGpu);
-    });
-
-    this->reset();
+    fMap.reset();
 }
 
 sk_sp<GrVkPipelineState> GrVkResourceProvider::PipelineStateCache::refPipelineState(
@@ -116,11 +99,7 @@
     }
     desc.finalize();
 
-    Entry* entry = nullptr;
-    if (Entry** entryptr = fHashTable.find(desc)) {
-        SkASSERT(*entryptr);
-        entry = *entryptr;
-    }
+    std::unique_ptr<Entry>* entry = fMap.find(desc);
     if (!entry) {
 #ifdef GR_PIPELINE_STATE_CACHE_STATS
         ++fCacheMisses;
@@ -136,23 +115,9 @@
         if (nullptr == pipelineState) {
             return nullptr;
         }
-        if (fCount < kMaxEntries) {
-            entry = new Entry;
-            fCount++;
-        } else {
-            SkASSERT(fCount == kMaxEntries);
-            entry = fLRUList.head();
-            fLRUList.remove(entry);
-            entry->fPipelineState->freeGPUResources(fGpu);
-            fHashTable.remove(entry->fPipelineState->getDesc());
-        }
-        entry->fPipelineState = std::move(pipelineState);
-        fHashTable.set(entry);
-        fLRUList.addToTail(entry);
-        return entry->fPipelineState;
-    } else {
-        fLRUList.remove(entry);
-        fLRUList.addToTail(entry);
+        entry = fMap.insert(desc, std::unique_ptr<Entry>(new Entry(fGpu,
+                                                                   std::move(pipelineState))));
+        return (*entry)->fPipelineState;
     }
-    return entry->fPipelineState;
+    return (*entry)->fPipelineState;
 }
diff --git a/src/gpu/vk/GrVkPipelineStateDataManager.h b/src/gpu/vk/GrVkPipelineStateDataManager.h
index 312c6c6..4a061a6 100644
--- a/src/gpu/vk/GrVkPipelineStateDataManager.h
+++ b/src/gpu/vk/GrVkPipelineStateDataManager.h
@@ -10,6 +10,7 @@
 
 #include "glsl/GrGLSLProgramDataManager.h"
 
+#include "SkAutoMalloc.h"
 #include "vk/GrVkUniformHandler.h"
 
 class GrVkGpu;
diff --git a/src/gpu/vk/GrVkResourceProvider.h b/src/gpu/vk/GrVkResourceProvider.h
index 024488a..903223a 100644
--- a/src/gpu/vk/GrVkResourceProvider.h
+++ b/src/gpu/vk/GrVkResourceProvider.h
@@ -16,9 +16,9 @@
 #include "GrVkRenderPass.h"
 #include "GrVkResource.h"
 #include "GrVkUtil.h"
+#include "SkLRUCache.h"
 #include "SkTArray.h"
 #include "SkTDynamicHash.h"
-#include "SkTHash.h"
 #include "SkTInternalLList.h"
 
 #include "vk/GrVkDefines.h"
@@ -183,11 +183,13 @@
 
         struct Entry;
 
-        void reset();
+        struct DescHash {
+            uint32_t operator()(const GrProgramDesc& desc) const {
+                return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0);
+            }
+        };
 
-        int                         fCount;
-        SkTHashTable<Entry*, const GrVkPipelineState::Desc&, Entry> fHashTable;
-        SkTInternalLList<Entry> fLRUList;
+        SkLRUCache<const GrVkPipelineState::Desc, std::unique_ptr<Entry>, DescHash> fMap;
 
         GrVkGpu*                    fGpu;
 
diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp
index 3071fc8..da73e5a 100644
--- a/src/gpu/vk/GrVkUtil.cpp
+++ b/src/gpu/vk/GrVkUtil.cpp
@@ -42,12 +42,12 @@
             // store the data is if it was B4G4R4A4 and swizzle in shaders
             *format = VK_FORMAT_B4G4R4A4_UNORM_PACK16;
             return true;
-        case kIndex_8_GrPixelConfig:
-            // No current vulkan support for this config
-            return false;
         case kAlpha_8_GrPixelConfig:
             *format = VK_FORMAT_R8_UNORM;
             return true;
+        case kGray_8_GrPixelConfig:
+            *format = VK_FORMAT_R8_UNORM;
+            return true;
         case kETC1_GrPixelConfig:
             // converting to ETC2 which is a superset of ETC1
             *format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index 7a71546..d2fc845 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -79,20 +79,6 @@
     return false;
 }
 
-#ifdef SK_SUPPORT_LEGACY_PREROLL
-void SkImage::preroll(GrContext* ctx) const {
-    // For now, and to maintain parity w/ previous pixelref behavior, we just force the image
-    // to produce a cached raster-bitmap form, so that drawing to a raster canvas should be fast.
-    //
-    SkBitmap bm;
-    SkColorSpace* legacyColorSpace = nullptr;
-    if (as_IB(this)->getROPixels(&bm, legacyColorSpace)) {
-        bm.lockPixels();
-        bm.unlockPixels();
-    }
-}
-#endif
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 SkAlphaType SkImage::alphaType() const {
@@ -106,9 +92,6 @@
 
 SkData* SkImage::encode(SkEncodedImageFormat type, int quality) const {
     SkBitmap bm;
-    // TODO: Right now, the encoders don't handle F16 or linearly premultiplied data. Once they do,
-    // we should decode in "color space aware" mode, then re-encode that. For now, work around this
-    // by asking for a legacy decode (which gives us the raw data in N32).
     SkColorSpace* legacyColorSpace = nullptr;
     if (as_IB(this)->getROPixels(&bm, legacyColorSpace)) {
         SkDynamicMemoryWStream buf;
@@ -126,9 +109,6 @@
 
     SkBitmap bm;
     SkAutoPixmapUnlock apu;
-    // TODO: Right now, the encoders don't handle F16 or linearly premultiplied data. Once they do,
-    // we should decode in "color space aware" mode, then re-encode that. For now, work around this
-    // by asking for a legacy decode (which gives us the raw data in N32).
     SkColorSpace* legacyColorSpace = nullptr;
     if (as_IB(this)->getROPixels(&bm, legacyColorSpace) &&
         bm.requestLock(&apu)) {
@@ -214,20 +194,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-static bool raster_canvas_supports(const SkImageInfo& info) {
-    switch (info.colorType()) {
-        case kN32_SkColorType:
-            return kUnpremul_SkAlphaType != info.alphaType();
-        case kRGB_565_SkColorType:
-            return true;
-        case kAlpha_8_SkColorType:
-            return true;
-        default:
-            break;
-    }
-    return false;
-}
-
 SkImage_Base::SkImage_Base(int width, int height, uint32_t uniqueID)
     : INHERITED(width, height, uniqueID)
     , fAddedToCache(false)
@@ -239,23 +205,6 @@
     }
 }
 
-bool SkImage_Base::onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                                int srcX, int srcY, CachingHint) const {
-    if (!raster_canvas_supports(dstInfo)) {
-        return false;
-    }
-
-    SkBitmap bm;
-    bm.installPixels(dstInfo, dstPixels, dstRowBytes);
-    SkCanvas canvas(bm);
-
-    SkPaint paint;
-    paint.setBlendMode(SkBlendMode::kSrc);
-    canvas.drawImage(this, -SkIntToScalar(srcX), -SkIntToScalar(srcY), &paint);
-
-    return true;
-}
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 bool SkImage::readPixels(const SkPixmap& pmap, int srcX, int srcY, CachingHint chint) const {
@@ -314,20 +263,18 @@
 }
 
 sk_sp<SkImage> SkImage::MakeFromPicture(sk_sp<SkPicture> picture, const SkISize& dimensions,
-                                        const SkMatrix* matrix, const SkPaint* paint,
-                                        sk_sp<SkColorSpace> colorSpace) {
-    if (!picture) {
-        return nullptr;
-    }
-    return MakeFromGenerator(SkImageGenerator::NewFromPicture(dimensions, picture.get(), matrix,
-                                                              paint, std::move(colorSpace)));
+                                        const SkMatrix* matrix, const SkPaint* paint) {
+    return SkImage::MakeFromPicture(std::move(picture), dimensions, matrix, paint, BitDepth::kU8,
+                                    nullptr);
 }
 
 sk_sp<SkImage> SkImage::MakeFromPicture(sk_sp<SkPicture> picture, const SkISize& dimensions,
-                                        const SkMatrix* matrix, const SkPaint* paint) {
-    return MakeFromPicture(std::move(picture), dimensions, matrix, paint, nullptr);
+                                        const SkMatrix* matrix, const SkPaint* paint,
+                                        BitDepth bitDepth, sk_sp<SkColorSpace> colorSpace) {
+    return MakeFromGenerator(SkImageGenerator::NewFromPicture(dimensions, picture.get(), matrix,
+                                                              paint, bitDepth,
+                                                              std::move(colorSpace)));
 }
-
 sk_sp<SkImage> SkImage::makeWithFilter(const SkImageFilter* filter, const SkIRect& subset,
                                        const SkIRect& clipBounds, SkIRect* outSubset,
                                        SkIPoint* offset) const {
diff --git a/src/image/SkImageShader.cpp b/src/image/SkImageShader.cpp
index e009b4a..1417ac8 100644
--- a/src/image/SkImageShader.cpp
+++ b/src/image/SkImageShader.cpp
@@ -8,7 +8,6 @@
 #include "SkBitmapController.h"
 #include "SkBitmapProcShader.h"
 #include "SkBitmapProvider.h"
-#include "SkColorShader.h"
 #include "SkColorTable.h"
 #include "SkEmptyShader.h"
 #include "SkFixedAlloc.h"
@@ -101,58 +100,16 @@
     return w > kMaxSize || h > kMaxSize;
 }
 
-// returns true and set color if the bitmap can be drawn as a single color
-// (for efficiency)
-static bool can_use_color_shader(const SkImage* image, SkColor* color) {
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    // HWUI does not support color shaders (see b/22390304)
-    return false;
-#endif
-
-    if (1 != image->width() || 1 != image->height()) {
-        return false;
-    }
-
-    SkPixmap pmap;
-    if (!image->peekPixels(&pmap)) {
-        return false;
-    }
-
-    switch (pmap.colorType()) {
-        case kN32_SkColorType:
-            *color = SkUnPreMultiply::PMColorToColor(*pmap.addr32(0, 0));
-            return true;
-        case kRGB_565_SkColorType:
-            *color = SkPixel16ToColor(*pmap.addr16(0, 0));
-            return true;
-        case kIndex_8_SkColorType: {
-            const SkColorTable& ctable = *pmap.ctable();
-            *color = SkUnPreMultiply::PMColorToColor(ctable[*pmap.addr8(0, 0)]);
-            return true;
-        }
-        default: // just skip the other configs for now
-            break;
-    }
-    return false;
-}
-
 sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image, TileMode tx, TileMode ty,
                                     const SkMatrix* localMatrix,
                                     SkTBlitterAllocator* allocator) {
     SkShader* shader;
-    SkColor color;
     if (!image || bitmap_is_too_big(image->width(), image->height())) {
         if (nullptr == allocator) {
             shader = new SkEmptyShader;
         } else {
             shader = allocator->createT<SkEmptyShader>();
         }
-    } else if (can_use_color_shader(image.get(), &color)) {
-        if (nullptr == allocator) {
-            shader = new SkColorShader(color);
-        } else {
-            shader = allocator->createT<SkColorShader>(color);
-        }
     } else {
         if (nullptr == allocator) {
             shader = new SkImageShader(image, tx, ty, localMatrix);
diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h
index ebb38d4..e839a05 100644
--- a/src/image/SkImage_Base.h
+++ b/src/image/SkImage_Base.h
@@ -40,9 +40,8 @@
 
     virtual const SkBitmap* onPeekBitmap() const { return nullptr; }
 
-    // Default impl calls onDraw
     virtual bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
-                              int srcX, int srcY, CachingHint) const;
+                              int srcX, int srcY, CachingHint) const = 0;
 
     virtual GrTexture* peekTexture() const { return nullptr; }
 #if SK_SUPPORT_GPU
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index c347a1f..2912f00 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -130,8 +130,8 @@
         // let the GPU perform this transformation for us
         flags = GrContext::kUnpremul_PixelOpsFlag;
     }
-    if (!fTexture->readPixels(srcX, srcY, info.width(), info.height(), config,
-                              pixels, rowBytes, flags)) {
+    if (!fTexture->readPixels(fColorSpace.get(), srcX, srcY, info.width(), info.height(), config,
+                              info.colorSpace(), pixels, rowBytes, flags)) {
         return false;
     }
     // do we have to manually fix-up the alpha channel?
@@ -298,7 +298,7 @@
 
     const SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height));
 
-    renderTargetContext->drawRect(GrNoClip(), paint, GrAA::kNo, SkMatrix::I(), rect);
+    renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect);
 
     if (!renderTargetContext->accessRenderTarget()) {
         return nullptr;
diff --git a/src/image/SkImage_Raster.cpp b/src/image/SkImage_Raster.cpp
index afd5f90..cce1ed2 100644
--- a/src/image/SkImage_Raster.cpp
+++ b/src/image/SkImage_Raster.cpp
@@ -92,9 +92,6 @@
                             sk_sp<SkColorSpace>*) const override;
     sk_sp<SkImage> onMakeSubset(const SkIRect&) const override;
 
-    // exposed for SkSurface_Raster via SkNewImageFromPixelRef
-    SkImage_Raster(const SkImageInfo&, SkPixelRef*, const SkIPoint& origin, size_t rowBytes);
-
     SkPixelRef* getPixelRef() const { return fBitmap.pixelRef(); }
 
     bool onAsLegacyBitmap(SkBitmap*, LegacyBitmapMode) const override;
@@ -153,16 +150,6 @@
     fBitmap.lockPixels();
 }
 
-SkImage_Raster::SkImage_Raster(const Info& info, SkPixelRef* pr, const SkIPoint& pixelRefOrigin,
-                               size_t rowBytes)
-    : INHERITED(info.width(), info.height(), pr->getGenerationID())
-{
-    fBitmap.setInfo(info, rowBytes);
-    fBitmap.setPixelRef(sk_ref_sp(pr), pixelRefOrigin.x(), pixelRefOrigin.y());
-    fBitmap.lockPixels();
-    SkASSERT(fBitmap.isImmutable());
-}
-
 SkImage_Raster::~SkImage_Raster() {
 #if SK_SUPPORT_GPU
     SkASSERT(nullptr == fPinnedTexture.get());  // want the caller to have manually unpinned
@@ -313,14 +300,6 @@
     return sk_make_sp<SkImage_Raster>(pmap.info(), std::move(data), pmap.rowBytes(), pmap.ctable());
 }
 
-sk_sp<SkImage> SkMakeImageFromPixelRef(const SkImageInfo& info, SkPixelRef* pr,
-                                       const SkIPoint& pixelRefOrigin, size_t rowBytes) {
-    if (!SkImage_Raster::ValidArgs(info, rowBytes, false, nullptr)) {
-        return nullptr;
-    }
-    return sk_make_sp<SkImage_Raster>(info, pr, pixelRefOrigin, rowBytes);
-}
-
 sk_sp<SkImage> SkMakeImageFromRasterBitmap(const SkBitmap& bm, SkCopyPixelsMode cpm,
                                            SkTBlitterAllocator* allocator) {
     bool hasColorTable = false;
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index 3d6670f..cb85708 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -161,13 +161,7 @@
 }
 
 sk_sp<SkImage> SkSurface::makeImageSnapshot(SkBudgeted budgeted) {
-    // the caller will call unref() to balance this
-    return asSB(this)->refCachedImage(budgeted, kNo_ForceUnique);
-}
-
-sk_sp<SkImage> SkSurface::makeImageSnapshot(SkBudgeted budgeted, ForceUnique unique) {
-    // the caller will call unref() to balance this
-    return asSB(this)->refCachedImage(budgeted, unique);
+    return asSB(this)->refCachedImage(budgeted);
 }
 
 sk_sp<SkSurface> SkSurface::makeSurface(const SkImageInfo& info) {
diff --git a/src/image/SkSurface_Base.h b/src/image/SkSurface_Base.h
index a8c1d8f..27d2350 100644
--- a/src/image/SkSurface_Base.h
+++ b/src/image/SkSurface_Base.h
@@ -43,7 +43,7 @@
      *  must faithfully represent the current contents, even if the surface
      *  is changed after this called (e.g. it is drawn to via its canvas).
      */
-    virtual sk_sp<SkImage> onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) = 0;
+    virtual sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) = 0;
 
     /**
      *  Default implementation:
@@ -81,7 +81,7 @@
     virtual void onPrepareForExternalIO() {}
 
     inline SkCanvas* getCachedCanvas();
-    inline sk_sp<SkImage> refCachedImage(SkBudgeted, ForceUnique);
+    inline sk_sp<SkImage> refCachedImage(SkBudgeted);
 
     bool hasCachedImage() const { return fCachedImage != nullptr; }
 
@@ -114,21 +114,16 @@
     return fCachedCanvas.get();
 }
 
-sk_sp<SkImage> SkSurface_Base::refCachedImage(SkBudgeted budgeted, ForceUnique unique) {
+sk_sp<SkImage> SkSurface_Base::refCachedImage(SkBudgeted budgeted) {
     SkImage* snap = fCachedImage;
-    if (kYes_ForceUnique == unique && snap && !snap->unique()) {
-        snap = nullptr;
-    }
     if (snap) {
         return sk_ref_sp(snap);
     }
-    SkCopyPixelsMode cpm = (kYes_ForceUnique == unique) ? kAlways_SkCopyPixelsMode :
-                                                          kIfMutable_SkCopyPixelsMode;
-    snap = this->onNewImageSnapshot(budgeted, cpm).release();
-    if (kNo_ForceUnique == unique) {
-        SkASSERT(!fCachedImage);
-        fCachedImage = SkSafeRef(snap);
-    }
+
+    snap = this->onNewImageSnapshot(budgeted).release();
+    SkASSERT(!fCachedImage);
+    fCachedImage = SkSafeRef(snap);
+
     SkASSERT(!fCachedCanvas || fCachedCanvas->getSurfaceBase() == this);
     return sk_sp<SkImage>(snap);
 }
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index c81c06e..d34c9b4 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -82,7 +82,7 @@
                                        origin, &this->props());
 }
 
-sk_sp<SkImage> SkSurface_Gpu::onNewImageSnapshot(SkBudgeted budgeted, SkCopyPixelsMode cpm) {
+sk_sp<SkImage> SkSurface_Gpu::onNewImageSnapshot(SkBudgeted budgeted) {
     GrRenderTargetContext* rtc = fDevice->accessRenderTargetContext();
     if (!rtc) {
         return nullptr;
@@ -95,7 +95,7 @@
     // 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.
-    if (kAlways_SkCopyPixelsMode == cpm || !srcProxy || rtc->priv().refsWrappedObjects()) {
+    if (!srcProxy || rtc->priv().refsWrappedObjects()) {
         GrSurfaceDesc desc = rtc->desc();
         desc.fFlags = desc.fFlags & ~kRenderTarget_GrSurfaceFlag;
 
@@ -136,7 +136,7 @@
     }
     // are we sharing our render target with the image? Note this call should never create a new
     // image because onCopyOnWrite is only called when there is a cached image.
-    sk_sp<SkImage> image(this->refCachedImage(SkBudgeted::kNo, kNo_ForceUnique));
+    sk_sp<SkImage> image(this->refCachedImage(SkBudgeted::kNo));
     SkASSERT(image);
     if (rt->asTexture() == as_IB(image)->peekTexture()) {
         this->fDevice->replaceRenderTargetContext(SkSurface::kRetain_ContentChangeMode == mode);
diff --git a/src/image/SkSurface_Gpu.h b/src/image/SkSurface_Gpu.h
index b7088ea..a625473 100644
--- a/src/image/SkSurface_Gpu.h
+++ b/src/image/SkSurface_Gpu.h
@@ -23,7 +23,7 @@
     bool onGetRenderTargetHandle(GrBackendObject*, BackendHandleAccess) override;
     SkCanvas* onNewCanvas() override;
     sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override;
-    sk_sp<SkImage> onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) override;
+    sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) override;
     void onCopyOnWrite(ContentChangeMode) override;
     void onDiscard() override;
     void onPrepareForExternalIO() override;
diff --git a/src/image/SkSurface_Raster.cpp b/src/image/SkSurface_Raster.cpp
index 308b072..47e0dbe 100644
--- a/src/image/SkSurface_Raster.cpp
+++ b/src/image/SkSurface_Raster.cpp
@@ -24,7 +24,7 @@
 
     SkCanvas* onNewCanvas() override;
     sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override;
-    sk_sp<SkImage> onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) override;
+    sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) override;
     void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override;
     void onCopyOnWrite(ContentChangeMode) override;
     void onRestoreBackingMutability() override;
@@ -130,7 +130,8 @@
     canvas->drawBitmap(fBitmap, x, y, paint);
 }
 
-sk_sp<SkImage> SkSurface_Raster::onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode cpm) {
+sk_sp<SkImage> SkSurface_Raster::onNewImageSnapshot(SkBudgeted) {
+    SkCopyPixelsMode cpm = kIfMutable_SkCopyPixelsMode;
     if (fWeOwnThePixels) {
         // SkImage_raster requires these pixels are immutable for its full lifetime.
         // We'll undo this via onRestoreBackingMutability() if we can avoid the COW.
@@ -155,7 +156,7 @@
 
 void SkSurface_Raster::onCopyOnWrite(ContentChangeMode mode) {
     // are we sharing pixelrefs with the image?
-    sk_sp<SkImage> cached(this->refCachedImage(SkBudgeted::kNo, kNo_ForceUnique));
+    sk_sp<SkImage> cached(this->refCachedImage(SkBudgeted::kNo));
     SkASSERT(cached);
     if (SkBitmapImageGetPixelRef(cached.get()) == fBitmap.pixelRef()) {
         SkASSERT(fWeOwnThePixels);
diff --git a/src/images/SkImageEncoder.cpp b/src/images/SkImageEncoder.cpp
index f135a02..fecadbf 100644
--- a/src/images/SkImageEncoder.cpp
+++ b/src/images/SkImageEncoder.cpp
@@ -16,10 +16,14 @@
         return SkEncodeImageWithWIC(dst, src, format, quality);
     #else
         switch(format) {
-            case SkEncodedImageFormat::kJPEG: return SkEncodeImageAsJPEG(dst, src, quality);
-            case SkEncodedImageFormat::kPNG:  return SkEncodeImageAsPNG(dst, src);
-            case SkEncodedImageFormat::kWEBP: return SkEncodeImageAsWEBP(dst, src, quality);
-            default:                          return false;
+            case SkEncodedImageFormat::kJPEG:
+                return SkEncodeImageAsJPEG(dst, src, quality);
+            case SkEncodedImageFormat::kPNG:
+                return SkEncodeImageAsPNG(dst, src, SkEncodeOptions());
+            case SkEncodedImageFormat::kWEBP:
+                return SkEncodeImageAsWEBP(dst, src, quality);
+            default:
+                return false;
         }
     #endif
 }
diff --git a/src/images/SkImageEncoderPriv.h b/src/images/SkImageEncoderPriv.h
index 22f8f4b..7748204 100644
--- a/src/images/SkImageEncoderPriv.h
+++ b/src/images/SkImageEncoderPriv.h
@@ -10,6 +10,18 @@
 
 #include "SkImageEncoder.h"
 
+struct SkEncodeOptions {
+    enum class PremulBehavior {
+         // Convert to a linear space before premultiplying or unpremultiplying.
+        kGammaCorrect,
+
+        // Ignore the transfer function when premultiplying or unpremultiplying.
+        kLegacy,
+    };
+
+    PremulBehavior fPremulBehavior = PremulBehavior::kLegacy;
+};
+
 #ifdef SK_HAS_JPEG_LIBRARY
     bool SkEncodeImageAsJPEG(SkWStream*, const SkPixmap&, int quality);
 #else
@@ -17,7 +29,7 @@
 #endif
 
 #ifdef SK_HAS_PNG_LIBRARY
-    bool SkEncodeImageAsPNG(SkWStream*, const SkPixmap&);
+    bool SkEncodeImageAsPNG(SkWStream*, const SkPixmap&, const SkEncodeOptions&);
 #else
     #define SkEncodeImageAsPNG(...) false
 #endif
diff --git a/src/images/SkPNGImageEncoder.cpp b/src/images/SkPNGImageEncoder.cpp
index 16df020..55aead2 100644
--- a/src/images/SkPNGImageEncoder.cpp
+++ b/src/images/SkPNGImageEncoder.cpp
@@ -21,29 +21,8 @@
 
 #include "png.h"
 
-/* These were dropped in libpng >= 1.4 */
-#ifndef png_infopp_NULL
-#define png_infopp_NULL nullptr
-#endif
-
-#ifndef png_bytepp_NULL
-#define png_bytepp_NULL nullptr
-#endif
-
-#ifndef int_p_NULL
-#define int_p_NULL nullptr
-#endif
-
-#ifndef png_flush_ptr_NULL
-#define png_flush_ptr_NULL nullptr
-#endif
-
-#define DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS true
 // Suppress most PNG warnings when calling image decode functions.
-static const bool c_suppressPNGImageDecoderWarnings{
-    DEFAULT_FOR_SUPPRESS_PNG_IMAGE_DECODER_WARNINGS};
-
-///////////////////////////////////////////////////////////////////////////////
+static const bool c_suppressPNGImageDecoderWarnings = true;
 
 static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
     if (!c_suppressPNGImageDecoderWarnings) {
@@ -59,49 +38,55 @@
     }
 }
 
-static transform_scanline_proc choose_proc(SkColorType ct, SkAlphaType alphaType) {
-    static const struct {
-        SkColorType             fColorType;
-        SkAlphaType             fAlphaType;
-        transform_scanline_proc fProc;
-    } gMap[] = {
-        { kRGB_565_SkColorType,   kOpaque_SkAlphaType,   transform_scanline_565    },
-        { kRGBA_8888_SkColorType, kOpaque_SkAlphaType,   transform_scanline_RGBX   },
-        { kBGRA_8888_SkColorType, kOpaque_SkAlphaType,   transform_scanline_BGRX   },
-        { kRGBA_8888_SkColorType, kPremul_SkAlphaType,   transform_scanline_rgbA   },
-        { kBGRA_8888_SkColorType, kPremul_SkAlphaType,   transform_scanline_bgrA   },
-        { kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, transform_scanline_memcpy },
-        { kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, transform_scanline_BGRA   },
-        { kARGB_4444_SkColorType, kOpaque_SkAlphaType,   transform_scanline_444    },
-        { kARGB_4444_SkColorType, kPremul_SkAlphaType,   transform_scanline_4444   },
-        { kIndex_8_SkColorType,   kOpaque_SkAlphaType,   transform_scanline_memcpy },
-        { kIndex_8_SkColorType,   kPremul_SkAlphaType,   transform_scanline_memcpy },
-        { kIndex_8_SkColorType,   kUnpremul_SkAlphaType, transform_scanline_memcpy },
-        { kGray_8_SkColorType,    kOpaque_SkAlphaType,   transform_scanline_memcpy },
-    };
-
-    for (auto entry : gMap) {
-        if (entry.fColorType == ct && entry.fAlphaType == alphaType) {
-            return entry.fProc;
-        }
+static transform_scanline_proc choose_proc(const SkImageInfo& info) {
+    const bool isGammaEncoded = info.gammaCloseToSRGB();
+    switch (info.colorType()) {
+        case kRGBA_8888_SkColorType:
+            switch (info.alphaType()) {
+                case kOpaque_SkAlphaType:
+                    return transform_scanline_RGBX;
+                case kUnpremul_SkAlphaType:
+                    return transform_scanline_memcpy;
+                case kPremul_SkAlphaType:
+                    return isGammaEncoded ? transform_scanline_srgbA :
+                                            transform_scanline_rgbA;
+                default:
+                    SkASSERT(false);
+                    return nullptr;
+            }
+        case kBGRA_8888_SkColorType:
+            switch (info.alphaType()) {
+                case kOpaque_SkAlphaType:
+                    return transform_scanline_BGRX;
+                case kUnpremul_SkAlphaType:
+                    return transform_scanline_BGRA;
+                case kPremul_SkAlphaType:
+                    return isGammaEncoded ? transform_scanline_sbgrA :
+                                            transform_scanline_bgrA;
+                default:
+                    SkASSERT(false);
+                    return nullptr;
+            }
+        case kRGB_565_SkColorType:
+            return transform_scanline_565;
+        case kARGB_4444_SkColorType:
+            switch (info.alphaType()) {
+                case kOpaque_SkAlphaType:
+                    return transform_scanline_444;
+                case kPremul_SkAlphaType:
+                    // 4444 is assumed to be legacy premul.
+                    return transform_scanline_4444;
+                default:
+                    SkASSERT(false);
+                    return nullptr;
+            }
+        case kIndex_8_SkColorType:
+        case kGray_8_SkColorType:
+            return transform_scanline_memcpy;
+        default:
+            SkASSERT(false);
+            return nullptr;
     }
-    sk_throw();
-    return nullptr;
-}
-
-// return the minimum legal bitdepth (by png standards) for this many colortable
-// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16,
-// we can use fewer bits per in png
-static int computeBitDepth(int colorCount) {
-#if 0
-    int bits = SkNextLog2(colorCount);
-    SkASSERT(bits >= 1 && bits <= 8);
-    // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8)
-    return SkNextPow2(bits);
-#else
-    // for the moment, we don't know how to pack bitdepth < 8
-    return 8;
-#endif
 }
 
 /*  Pack palette[] with the corresponding colors, and if the image has alpha, also
@@ -109,29 +94,28 @@
     opaque, the return value will always be 0.
 */
 static inline int pack_palette(SkColorTable* ctable, png_color* SK_RESTRICT palette,
-                               png_byte* SK_RESTRICT alphas, SkAlphaType alphaType) {
-    const SkPMColor* SK_RESTRICT colors = ctable->readColors();
+                               png_byte* SK_RESTRICT alphas, const SkImageInfo& info) {
+    const SkPMColor* colors = ctable->readColors();
     const int count = ctable->count();
-    int numWithAlpha = 0;
-    if (kOpaque_SkAlphaType != alphaType) {
-        auto getUnpremulColor = [alphaType](uint8_t color, uint8_t alpha) {
-            if (kPremul_SkAlphaType == alphaType) {
-                const SkUnPreMultiply::Scale* table = SkUnPreMultiply::GetScaleTable();
-                const SkUnPreMultiply::Scale scale = table[alpha];
-                return (uint8_t) SkUnPreMultiply::ApplyScale(scale, color);
-            } else {
-                return color;
-            }
-        };
+    SkPMColor storage[256];
+    if (kPremul_SkAlphaType == info.alphaType()) {
+        // Unpremultiply the colors.
+        const SkImageInfo rgbaInfo = info.makeColorType(kRGBA_8888_SkColorType);
+        transform_scanline_proc proc = choose_proc(rgbaInfo);
+        proc((char*) storage, (const char*) colors, ctable->count(), 4);
+        colors = storage;
+    }
 
+    int numWithAlpha = 0;
+    if (kOpaque_SkAlphaType != info.alphaType()) {
         // PNG requires that all non-opaque colors come first in the palette.  Write these first.
         for (int i = 0; i < count; i++) {
             uint8_t alpha = SkGetPackedA32(colors[i]);
             if (0xFF != alpha) {
                 alphas[numWithAlpha] = alpha;
-                palette[numWithAlpha].red   = getUnpremulColor(SkGetPackedR32(colors[i]), alpha);
-                palette[numWithAlpha].green = getUnpremulColor(SkGetPackedG32(colors[i]), alpha);
-                palette[numWithAlpha].blue  = getUnpremulColor(SkGetPackedB32(colors[i]), alpha);
+                palette[numWithAlpha].red   = SkGetPackedR32(colors[i]);
+                palette[numWithAlpha].green = SkGetPackedG32(colors[i]);
+                palette[numWithAlpha].blue  = SkGetPackedB32(colors[i]);
                 numWithAlpha++;
             }
         }
@@ -168,12 +152,24 @@
 
 static bool do_encode(SkWStream*, const SkPixmap&, int, int, png_color_8&);
 
-bool SkEncodeImageAsPNG(SkWStream* stream, const SkPixmap& pixmap) {
+bool SkEncodeImageAsPNG(SkWStream* stream, const SkPixmap& src, const SkEncodeOptions& opts) {
+    SkASSERT(!src.colorSpace() || src.colorSpace()->gammaCloseToSRGB() ||
+             src.colorSpace()->gammaIsLinear());
+
+    SkPixmap pixmap = src;
+    if (SkEncodeOptions::PremulBehavior::kLegacy == opts.fPremulBehavior) {
+        pixmap.setColorSpace(nullptr);
+    } else {
+        if (!pixmap.colorSpace()) {
+            return false;
+        }
+    }
+
     if (!pixmap.addr() || pixmap.info().isEmpty()) {
         return false;
     }
-    const SkColorType ct = pixmap.colorType();
-    switch (ct) {
+    const SkColorType colorType = pixmap.colorType();
+    switch (colorType) {
         case kIndex_8_SkColorType:
         case kGray_8_SkColorType:
         case kRGBA_8888_SkColorType:
@@ -188,7 +184,7 @@
     const SkAlphaType alphaType = pixmap.alphaType();
     switch (alphaType) {
         case kUnpremul_SkAlphaType:
-            if (kARGB_4444_SkColorType == ct) {
+            if (kARGB_4444_SkColorType == colorType) {
                 return false;
             }
 
@@ -201,22 +197,22 @@
     }
 
     const bool isOpaque = (kOpaque_SkAlphaType == alphaType);
-    int bitDepth = 8;   // default for color
+    const int bitDepth = 8;
     png_color_8 sig_bit;
     sk_bzero(&sig_bit, sizeof(png_color_8));
 
-    int colorType;
-    switch (ct) {
+    int pngColorType;
+    switch (colorType) {
         case kIndex_8_SkColorType:
             sig_bit.red = 8;
             sig_bit.green = 8;
             sig_bit.blue = 8;
             sig_bit.alpha = 8;
-            colorType = PNG_COLOR_TYPE_PALETTE;
+            pngColorType = PNG_COLOR_TYPE_PALETTE;
             break;
         case kGray_8_SkColorType:
             sig_bit.gray = 8;
-            colorType = PNG_COLOR_TYPE_GRAY;
+            pngColorType = PNG_COLOR_TYPE_GRAY;
             SkASSERT(isOpaque);
             break;
         case kRGBA_8888_SkColorType:
@@ -225,41 +221,40 @@
             sig_bit.green = 8;
             sig_bit.blue = 8;
             sig_bit.alpha = 8;
-            colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+            pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
             break;
         case kARGB_4444_SkColorType:
             sig_bit.red = 4;
             sig_bit.green = 4;
             sig_bit.blue = 4;
             sig_bit.alpha = 4;
-            colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+            pngColorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
             break;
         case kRGB_565_SkColorType:
             sig_bit.red = 5;
             sig_bit.green = 6;
             sig_bit.blue = 5;
-            colorType = PNG_COLOR_TYPE_RGB;
+            pngColorType = PNG_COLOR_TYPE_RGB;
             SkASSERT(isOpaque);
             break;
         default:
             return false;
     }
-    if (kIndex_8_SkColorType == ct) {
+    if (kIndex_8_SkColorType == colorType) {
         SkColorTable* ctable = pixmap.ctable();
         if (!ctable || ctable->count() == 0) {
             return false;
         }
-        // check if we can store in fewer than 8 bits
-        bitDepth = computeBitDepth(ctable->count());
+
+        // Currently, we always use 8-bit indices for paletted pngs.
+        // When ctable->count() <= 16, we could potentially use 1, 2,
+        // or 4 bit indices.
     }
-    return do_encode(stream, pixmap, colorType, bitDepth, sig_bit);
+    return do_encode(stream, pixmap, pngColorType, bitDepth, sig_bit);
 }
 
 static bool do_encode(SkWStream* stream, const SkPixmap& pixmap,
-                      int colorType, int bitDepth, png_color_8& sig_bit) {
-    SkAlphaType alphaType = pixmap.alphaType();
-    SkColorType ct = pixmap.colorType();
-
+                      int pngColorType, int bitDepth, png_color_8& sig_bit) {
     png_structp png_ptr;
     png_infop info_ptr;
 
@@ -270,7 +265,7 @@
 
     info_ptr = png_create_info_struct(png_ptr);
     if (nullptr == info_ptr) {
-        png_destroy_write_struct(&png_ptr,  png_infopp_NULL);
+        png_destroy_write_struct(&png_ptr,  nullptr);
         return false;
     }
 
@@ -282,7 +277,7 @@
         return false;
     }
 
-    png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
+    png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, nullptr);
 
     /* Set the image information here.  Width and height are up to 2^31,
     * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
@@ -294,17 +289,17 @@
     */
 
     png_set_IHDR(png_ptr, info_ptr, pixmap.width(), pixmap.height(),
-                 bitDepth, colorType,
+                 bitDepth, pngColorType,
                  PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
                  PNG_FILTER_TYPE_BASE);
 
     // set our colortable/trans arrays if needed
     png_color paletteColors[256];
     png_byte trans[256];
-    if (kIndex_8_SkColorType == ct) {
+    if (kIndex_8_SkColorType == pixmap.colorType()) {
         SkColorTable* colorTable = pixmap.ctable();
         SkASSERT(colorTable);
-        int numTrans = pack_palette(colorTable, paletteColors, trans, alphaType);
+        int numTrans = pack_palette(colorTable, paletteColors, trans, pixmap.info());
         png_set_PLTE(png_ptr, info_ptr, paletteColors, colorTable->count());
         if (numTrans > 0) {
             png_set_tRNS(png_ptr, info_ptr, trans, numTrans, nullptr);
@@ -317,11 +312,11 @@
     const char* srcImage = (const char*)pixmap.addr();
     SkAutoSTMalloc<1024, char> rowStorage(pixmap.width() << 2);
     char* storage = rowStorage.get();
-    transform_scanline_proc proc = choose_proc(ct, alphaType);
+    transform_scanline_proc proc = choose_proc(pixmap.info());
 
     for (int y = 0; y < pixmap.height(); y++) {
         png_bytep row_ptr = (png_bytep)storage;
-        proc(storage, srcImage, pixmap.width(), SkColorTypeBytesPerPixel(ct));
+        proc(storage, srcImage, pixmap.width(), SkColorTypeBytesPerPixel(pixmap.colorType()));
         png_write_rows(png_ptr, &row_ptr, 1);
         srcImage += pixmap.rowBytes();
     }
diff --git a/src/images/transform_scanline.h b/src/images/transform_scanline.h
index a02e98a..1ce67cd 100644
--- a/src/images/transform_scanline.h
+++ b/src/images/transform_scanline.h
@@ -13,6 +13,7 @@
 #include "SkColor.h"
 #include "SkColorPriv.h"
 #include "SkPreConfig.h"
+#include "SkRasterPipeline.h"
 #include "SkUnPreMultiply.h"
 
 /**
@@ -128,19 +129,51 @@
 }
 
 /**
+ * Transform from legacy kPremul, kRGBA_8888_SkColorType to 4-bytes-per-pixel unpremultiplied RGBA.
+ */
+static void transform_scanline_rgbA(char* SK_RESTRICT dst, const char* SK_RESTRICT src, int width,
+                                    int bpp) {
+    transform_scanline_unpremultiply<true>(dst, src, width, bpp);
+}
+
+/**
+ * Transform from legacy kPremul, kBGRA_8888_SkColorType to 4-bytes-per-pixel unpremultiplied RGBA.
+ */
+static void transform_scanline_bgrA(char* SK_RESTRICT dst, const char* SK_RESTRICT src, int width,
+                                    int bpp) {
+    transform_scanline_unpremultiply<false>(dst, src, width, bpp);
+}
+
+template <bool kIsRGBA>
+static inline void transform_scanline_unpremultiply_sRGB(void* dst, const void* src, int width, int)
+{
+    SkRasterPipeline p;
+    p.append(SkRasterPipeline::load_8888, &src);
+    if (!kIsRGBA) {
+        p.append(SkRasterPipeline::swap_rb);
+    }
+
+    p.append_from_srgb(kPremul_SkAlphaType);
+    p.append(SkRasterPipeline::unpremul);
+    p.append(SkRasterPipeline::to_srgb);
+    p.append(SkRasterPipeline::store_8888, &dst);
+    p.run(0, 0, width);
+}
+
+/**
  * Transform from kPremul, kRGBA_8888_SkColorType to 4-bytes-per-pixel unpremultiplied RGBA.
  */
-static void transform_scanline_rgbA(char* SK_RESTRICT dst, const char* SK_RESTRICT src,
-                                    int width, int bpp) {
-    transform_scanline_unpremultiply<true>(dst, src, width, bpp);
+static void transform_scanline_srgbA(char* SK_RESTRICT dst, const char* SK_RESTRICT src,
+                                     int width, int bpp) {
+    transform_scanline_unpremultiply_sRGB<true>(dst, src, width, bpp);
 }
 
 /**
  * Transform from kPremul, kBGRA_8888_SkColorType to 4-bytes-per-pixel unpremultiplied RGBA.
  */
-static void transform_scanline_bgrA(char* SK_RESTRICT dst, const char* SK_RESTRICT src,
-                                    int width, int bpp) {
-    transform_scanline_unpremultiply<false>(dst, src, width, bpp);
+static void transform_scanline_sbgrA(char* SK_RESTRICT dst, const char* SK_RESTRICT src,
+                                     int width, int bpp) {
+    transform_scanline_unpremultiply_sRGB<false>(dst, src, width, bpp);
 }
 
 /**
diff --git a/src/opts/SkBitmapProcState_matrix_neon.h b/src/opts/SkBitmapProcState_matrix_neon.h
index a7f4753..fb91547 100644
--- a/src/opts/SkBitmapProcState_matrix_neon.h
+++ b/src/opts/SkBitmapProcState_matrix_neon.h
@@ -54,9 +54,10 @@
 
 #ifdef CHECK_FOR_DECAL
     // test if we don't need to apply the tile proc
-    if (can_truncate_to_fixed_for_decal(fx, dx, count, maxX)) {
-        decal_nofilter_scale_neon(xy, SkFractionalIntToFixed(fx),
-                             SkFractionalIntToFixed(dx), count);
+    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
@@ -309,9 +310,10 @@
 
 #ifdef CHECK_FOR_DECAL
     // test if we don't need to apply the tile proc
-    if (can_truncate_to_fixed_for_decal(fx, dx, count, maxX)) {
-        decal_filter_scale_neon(xy, SkFractionalIntToFixed(fx),
-                             SkFractionalIntToFixed(dx), count);
+    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
diff --git a/src/opts/SkBitmapProcState_opts_SSE2.cpp b/src/opts/SkBitmapProcState_opts_SSE2.cpp
index e45c4ba..fa1e042 100644
--- a/src/opts/SkBitmapProcState_opts_SSE2.cpp
+++ b/src/opts/SkBitmapProcState_opts_SSE2.cpp
@@ -7,6 +7,7 @@
 
 #include <emmintrin.h>
 #include "SkBitmapProcState_opts_SSE2.h"
+#include "SkBitmapProcState_utils.h"
 #include "SkColorPriv.h"
 #include "SkPaint.h"
 #include "SkUtils.h"
@@ -262,8 +263,7 @@
     SkFixed fx = mapper.fixedX();
 
     // test if we don't need to apply the tile proc
-    if (dx > 0 && (unsigned)(fx >> 16) <= maxX &&
-        (unsigned)((fx + dx * (count - 1)) >> 16) < maxX) {
+    if (can_truncate_to_fixed_for_decal(fx, dx, count, maxX)) {
         if (count >= 4) {
             // SSE version of decal_filter_scale
             while ((size_t(xy) & 0x0F) != 0) {
diff --git a/src/opts/SkNx_sse.h b/src/opts/SkNx_sse.h
index 3941156..3dc60f7 100644
--- a/src/opts/SkNx_sse.h
+++ b/src/opts/SkNx_sse.h
@@ -567,10 +567,10 @@
                    _26 = unpacklo_pd(rg2367, ba2367),  // r2 ...      | r6 ...
                    _37 = unpackhi_pd(rg2367, ba2367);  // r3 ...      | r7 ...
 
-            __m256 _01 = _mm256_permute2f128_ps(_04, _15, 16),  // 16 == 010 000 == lo, lo
-                   _23 = _mm256_permute2f128_ps(_26, _37, 16),
-                   _45 = _mm256_permute2f128_ps(_04, _15, 25),  // 25 == 011 001 == hi, hi
-                   _67 = _mm256_permute2f128_ps(_26, _37, 25);
+            __m256 _01 = _mm256_permute2f128_ps(_04, _15, 32),  // 32 == 0010 0000 == lo, lo
+                   _23 = _mm256_permute2f128_ps(_26, _37, 32),
+                   _45 = _mm256_permute2f128_ps(_04, _15, 49),  // 49 == 0011 0001 == hi, hi
+                   _67 = _mm256_permute2f128_ps(_26, _37, 49);
 
             _mm256_storeu_ps((float*)ptr + 0*8, _01);
             _mm256_storeu_ps((float*)ptr + 1*8, _23);
diff --git a/src/opts/SkOpts_hsw.cpp b/src/opts/SkOpts_hsw.cpp
index d0845c5..bb4e4d1 100644
--- a/src/opts/SkOpts_hsw.cpp
+++ b/src/opts/SkOpts_hsw.cpp
@@ -6,8 +6,14 @@
  */
 
 #include "SkSafe_math.h"   // Keep this first.
-#include "SkOpts.h"
 
+// Please note carefully.
+// It is not safe for _opts.h files included here to use STL types, for the
+// same reason we just had to include SkSafe_math.h: STL types are templated,
+// defined in headers, but not in anonymous namespaces.  It's very easy to
+// cause ODR violations with these types and AVX+ code generation.
+
+#include "SkOpts.h"
 #define SK_OPTS_NS hsw
 #include "SkBitmapFilter_opts.h"
 #include "SkRasterPipeline_opts.h"
diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h
index e011541..143a9ec 100644
--- a/src/opts/SkRasterPipeline_opts.h
+++ b/src/opts/SkRasterPipeline_opts.h
@@ -19,7 +19,6 @@
 #include "SkPM4fPriv.h"
 #include "SkRasterPipeline.h"
 #include "SkSRGB.h"
-#include <utility>
 
 namespace {
 
@@ -35,58 +34,97 @@
     using SkNh = SkNx<N, uint16_t>;
     using SkNb = SkNx<N, uint8_t>;
 
-    struct Stage;
-    using Fn = void(SK_VECTORCALL *)(Stage*, size_t x_tail, SkNf,SkNf,SkNf,SkNf,
-                                                            SkNf,SkNf,SkNf,SkNf);
-    struct Stage { Fn next; void* ctx; };
-
+    using Fn = void(SK_VECTORCALL *)(size_t x_tail, void** p, SkNf,SkNf,SkNf,SkNf,
+                                                              SkNf,SkNf,SkNf,SkNf);
     // x_tail encodes two values x and tail as x*N+tail, where 0 <= tail < N.
     // x is the induction variable we're walking along, incrementing by N each step.
     // tail == 0 means work with a full N pixels; otherwise use only the low tail pixels.
+    //
+    // p is our program, a sequence of Fn to call interlaced with any void* context pointers.  E.g.
+    //    &load_8888
+    //    (src ptr)
+    //    &from_srgb
+    //    &move_src_dst
+    //    &load_f16
+    //    (dst ptr)
+    //    &swap
+    //    &srcover
+    //    &store_f16
+    //    (dst ptr)
+    //    &just_return
 
 }  // namespace
 
 #define SI static inline
 
+// Basically, return *(*ptr)++, maybe faster than the compiler can do it.
+SI void* load_and_increment(void*** ptr) {
+    // We do this often enough that it's worth hyper-optimizing.
+    // x86 can do this in one instruction if ptr is in rsi.
+    // (This is why p is the second argument to Fn: it's passed in rsi.)
+#if defined(__GNUC__) && defined(__x86_64__)
+    void* rax;
+    __asm__("lodsq" : "=a"(rax), "+S"(*ptr));
+    return rax;
+#else
+    return *(*ptr)++;
+#endif
+}
+
 // Stages are logically a pipeline, and physically are contiguous in an array.
 // To get to the next stage, we just increment our pointer to the next array element.
-SI void SK_VECTORCALL next(Stage* st, size_t x_tail, SkNf  r, SkNf  g, SkNf  b, SkNf  a,
-                                                     SkNf dr, SkNf dg, SkNf db, SkNf da) {
-    st->next(st+1, x_tail, r,g,b,a, dr,dg,db,da);
+SI void SK_VECTORCALL next(size_t x_tail, void** p, SkNf  r, SkNf  g, SkNf  b, SkNf  a,
+                                                    SkNf dr, SkNf dg, SkNf db, SkNf da) {
+    auto next = (Fn)load_and_increment(&p);
+    next(x_tail,p, r,g,b,a, dr,dg,db,da);
 }
 
 // Stages defined below always call next.
 // This is always the last stage, a backstop that actually returns to the caller when done.
-SI void SK_VECTORCALL just_return(Stage*, size_t, SkNf, SkNf, SkNf, SkNf,
+SI void SK_VECTORCALL just_return(size_t, void**, SkNf, SkNf, SkNf, SkNf,
                                                   SkNf, SkNf, SkNf, SkNf) {}
 
 #define STAGE(name)                                                                      \
-    static SK_ALWAYS_INLINE void name##_kernel(void* ctx, size_t x, size_t tail,         \
+    static SK_ALWAYS_INLINE void name##_kernel(size_t x, size_t tail,                    \
                                                SkNf&  r, SkNf&  g, SkNf&  b, SkNf&  a,   \
                                                SkNf& dr, SkNf& dg, SkNf& db, SkNf& da);  \
-    SI void SK_VECTORCALL name(Stage* st, size_t x_tail,                                 \
+    SI void SK_VECTORCALL name(size_t x_tail, void** p,                                  \
                                SkNf  r, SkNf  g, SkNf  b, SkNf  a,                       \
                                SkNf dr, SkNf dg, SkNf db, SkNf da) {                     \
-        name##_kernel(st->ctx, x_tail/N, x_tail%N, r,g,b,a, dr,dg,db,da);                \
-        next(st, x_tail, r,g,b,a, dr,dg,db,da);                                          \
+        name##_kernel(x_tail/N, x_tail%N, r,g,b,a, dr,dg,db,da);                         \
+        next(x_tail,p, r,g,b,a, dr,dg,db,da);                                            \
     }                                                                                    \
-    static SK_ALWAYS_INLINE void name##_kernel(void* ctx, size_t x, size_t tail,         \
+    static SK_ALWAYS_INLINE void name##_kernel(size_t x, size_t tail,                    \
                                                SkNf&  r, SkNf&  g, SkNf&  b, SkNf&  a,   \
                                                SkNf& dr, SkNf& dg, SkNf& db, SkNf& da)
 
+#define STAGE_CTX(name, Ctx)                                                             \
+    static SK_ALWAYS_INLINE void name##_kernel(Ctx ctx, size_t x, size_t tail,           \
+                                               SkNf&  r, SkNf&  g, SkNf&  b, SkNf&  a,   \
+                                               SkNf& dr, SkNf& dg, SkNf& db, SkNf& da);  \
+    SI void SK_VECTORCALL name(size_t x_tail, void** p,                                  \
+                               SkNf  r, SkNf  g, SkNf  b, SkNf  a,                       \
+                               SkNf dr, SkNf dg, SkNf db, SkNf da) {                     \
+        auto ctx = (Ctx)load_and_increment(&p);                                          \
+        name##_kernel(ctx, x_tail/N, x_tail%N, r,g,b,a, dr,dg,db,da);                    \
+        next(x_tail,p, r,g,b,a, dr,dg,db,da);                                            \
+    }                                                                                    \
+    static SK_ALWAYS_INLINE void name##_kernel(Ctx ctx, size_t x, size_t tail,           \
+                                               SkNf&  r, SkNf&  g, SkNf&  b, SkNf&  a,   \
+                                               SkNf& dr, SkNf& dg, SkNf& db, SkNf& da)
 
 // Many xfermodes apply the same logic to each channel.
 #define RGBA_XFERMODE(name)                                                     \
     static SK_ALWAYS_INLINE SkNf name##_kernel(const SkNf& s, const SkNf& sa,   \
                                                const SkNf& d, const SkNf& da);  \
-    SI void SK_VECTORCALL name(Stage* st, size_t x_tail,                        \
+    SI void SK_VECTORCALL name(size_t x_tail, void** p,                         \
                                SkNf  r, SkNf  g, SkNf  b, SkNf  a,              \
                                SkNf dr, SkNf dg, SkNf db, SkNf da) {            \
         r = name##_kernel(r,a,dr,da);                                           \
         g = name##_kernel(g,a,dg,da);                                           \
         b = name##_kernel(b,a,db,da);                                           \
         a = name##_kernel(a,a,da,da);                                           \
-        next(st, x_tail, r,g,b,a, dr,dg,db,da);                                 \
+        next(x_tail,p, r,g,b,a, dr,dg,db,da);                                   \
     }                                                                           \
     static SK_ALWAYS_INLINE SkNf name##_kernel(const SkNf& s, const SkNf& sa,   \
                                                const SkNf& d, const SkNf& da)
@@ -95,14 +133,14 @@
 #define RGB_XFERMODE(name)                                                      \
     static SK_ALWAYS_INLINE SkNf name##_kernel(const SkNf& s, const SkNf& sa,   \
                                                const SkNf& d, const SkNf& da);  \
-    SI void SK_VECTORCALL name(Stage* st, size_t x_tail,                         \
+    SI void SK_VECTORCALL name(size_t x_tail, void** p,                         \
                                SkNf  r, SkNf  g, SkNf  b, SkNf  a,              \
                                SkNf dr, SkNf dg, SkNf db, SkNf da) {            \
         r = name##_kernel(r,a,dr,da);                                           \
         g = name##_kernel(g,a,dg,da);                                           \
         b = name##_kernel(b,a,db,da);                                           \
         a = a + (da * (1.0f-a));                                                \
-        next(st, x_tail, r,g,b,a, dr,dg,db,da);                                 \
+        next(x_tail,p, r,g,b,a, dr,dg,db,da);                                   \
     }                                                                           \
     static SK_ALWAYS_INLINE SkNf name##_kernel(const SkNf& s, const SkNf& sa,   \
                                                const SkNf& d, const SkNf& da)
@@ -274,8 +312,8 @@
     *a = SkHalfToFloat_finite_ftz(ah);
 }
 
-STAGE(trace) {
-    SkDebugf("%s\n", (const char*)ctx);
+STAGE_CTX(trace, const char*) {
+    SkDebugf("%s\n", ctx);
 }
 STAGE(registers) {
     auto print = [](const char* name, const SkNf& v) {
@@ -313,12 +351,6 @@
     g = SkNf::Min(g, a);
     b = SkNf::Min(b, a);
 }
-STAGE(clamp_a_d) {
-    da = SkNf::Min(da, 1.0f);
-    dr = SkNf::Min(dr, da);
-    dg = SkNf::Min(dg, da);
-    db = SkNf::Min(db, da);
-}
 
 STAGE(unpremul) {
     auto scale = (a == 0.0f).thenElse(0.0f, 1.0f/a);
@@ -332,12 +364,12 @@
     b *= a;
 }
 
-STAGE(set_rgb) {
-    auto rgb = (const float*)ctx;
-    r = rgb[0];
-    g = rgb[1];
-    b = rgb[2];
+STAGE_CTX(set_rgb, const float*) {
+    r = ctx[0];
+    g = ctx[1];
+    b = ctx[2];
 }
+STAGE(swap_rb) { SkTSwap(r,b); }
 
 STAGE(move_src_dst) {
     dr = r;
@@ -351,20 +383,18 @@
     b = db;
     a = da;
 }
-
-STAGE(swap_rb)   { SkTSwap( r,  b); }
-STAGE(swap_rb_d) { SkTSwap(dr, db); }
+STAGE(swap) {
+    SkTSwap(r,dr);
+    SkTSwap(g,dg);
+    SkTSwap(b,db);
+    SkTSwap(a,da);
+}
 
 STAGE(from_srgb) {
     r = sk_linear_from_srgb_math(r);
     g = sk_linear_from_srgb_math(g);
     b = sk_linear_from_srgb_math(b);
 }
-STAGE(from_srgb_d) {
-    dr = sk_linear_from_srgb_math(dr);
-    dg = sk_linear_from_srgb_math(dg);
-    db = sk_linear_from_srgb_math(db);
-}
 STAGE(to_srgb) {
     r = sk_linear_to_srgb_needs_round(r);
     g = sk_linear_to_srgb_needs_round(g);
@@ -381,7 +411,7 @@
         // x^(141/64) = x^(128/64) * x^(12/64) * x^(1/64)
         return SkNf::Max((x*x) * (x16*x16*x16) * (x64), 0.0f);
     };
-    
+
     r = from_2dot2(r);
     g = from_2dot2(g);
     b = from_2dot2(b);
@@ -403,17 +433,16 @@
 }
 
 // The default shader produces a constant color (from the SkPaint).
-STAGE(constant_color) {
-    auto color = (const SkPM4f*)ctx;
-    r = color->r();
-    g = color->g();
-    b = color->b();
-    a = color->a();
+STAGE_CTX(constant_color, const SkPM4f*) {
+    r = ctx->r();
+    g = ctx->g();
+    b = ctx->b();
+    a = ctx->a();
 }
 
 // s' = sc for a scalar c.
-STAGE(scale_1_float) {
-    SkNf c = *(const float*)ctx;
+STAGE_CTX(scale_1_float, const float*) {
+    SkNf c = *ctx;
 
     r *= c;
     g *= c;
@@ -421,10 +450,10 @@
     a *= c;
 }
 // s' = sc for 8-bit c.
-STAGE(scale_u8) {
-    auto ptr = *(const uint8_t**)ctx + x;
-
+STAGE_CTX(scale_u8, const uint8_t**) {
+    auto ptr = *ctx + x;
     SkNf c = SkNf_from_byte(load(tail, ptr));
+
     r = r*c;
     g = g*c;
     b = b*c;
@@ -436,8 +465,8 @@
 }
 
 // s' = d(1-c) + sc, for a scalar c.
-STAGE(lerp_1_float) {
-    SkNf c = *(const float*)ctx;
+STAGE_CTX(lerp_1_float, const float*) {
+    SkNf c = *ctx;
 
     r = lerp(dr, r, c);
     g = lerp(dg, g, c);
@@ -446,10 +475,10 @@
 }
 
 // s' = d(1-c) + sc for 8-bit c.
-STAGE(lerp_u8) {
-    auto ptr = *(const uint8_t**)ctx + x;
-
+STAGE_CTX(lerp_u8, const uint8_t**) {
+    auto ptr = *ctx + x;
     SkNf c = SkNf_from_byte(load(tail, ptr));
+
     r = lerp(dr, r, c);
     g = lerp(dg, g, c);
     b = lerp(db, b, c);
@@ -457,8 +486,8 @@
 }
 
 // s' = d(1-c) + sc for 565 c.
-STAGE(lerp_565) {
-    auto ptr = *(const uint16_t**)ctx + x;
+STAGE_CTX(lerp_565, const uint16_t**) {
+    auto ptr = *ctx + x;
     SkNf cr, cg, cb;
     from_565(load(tail, ptr), &cr, &cg, &cb);
 
@@ -468,26 +497,21 @@
     a = 1.0f;
 }
 
-STAGE(load_565) {
-    auto ptr = *(const uint16_t**)ctx + x;
+STAGE_CTX(load_565, const uint16_t**) {
+    auto ptr = *ctx + x;
     from_565(load(tail, ptr), &r,&g,&b);
     a = 1.0f;
 }
-STAGE(load_565_d) {
-    auto ptr = *(const uint16_t**)ctx + x;
-    from_565(load(tail, ptr), &dr,&dg,&db);
-    da = 1.0f;
-}
-STAGE(store_565) {
-    auto ptr = *(uint16_t**)ctx + x;
+STAGE_CTX(store_565, uint16_t**) {
+    auto ptr = *ctx + x;
     store(tail, SkNx_cast<uint16_t>( SkNf_round(r, SK_R16_MASK) << SK_R16_SHIFT
                                    | SkNf_round(g, SK_G16_MASK) << SK_G16_SHIFT
                                    | SkNf_round(b, SK_B16_MASK) << SK_B16_SHIFT), ptr);
 }
 
 
-STAGE(load_f16) {
-    auto ptr = *(const uint64_t**)ctx + x;
+STAGE_CTX(load_f16, const uint64_t**) {
+    auto ptr = *ctx + x;
 
     const void* src = ptr;
     SkNx<N, uint64_t> px;
@@ -497,19 +521,8 @@
     }
     from_f16(src, &r, &g, &b, &a);
 }
-STAGE(load_f16_d) {
-    auto ptr = *(const uint64_t**)ctx + x;
-
-    const void* src = ptr;
-    SkNx<N, uint64_t> px;
-    if (tail) {
-        px = load(tail, ptr);
-        src = &px;
-    }
-    from_f16(src, &dr, &dg, &db, &da);
-}
-STAGE(store_f16) {
-    auto ptr = *(uint64_t**)ctx + x;
+STAGE_CTX(store_f16, uint64_t**) {
+    auto ptr = *ctx + x;
 
     SkNx<N, uint64_t> px;
     SkNh::Store4(tail ? (void*)&px : (void*)ptr, SkFloatToHalf_finite_ftz(r),
@@ -521,8 +534,8 @@
     }
 }
 
-STAGE(store_f32) {
-    auto ptr = *(SkPM4f**)ctx + x;
+STAGE_CTX(store_f32, SkPM4f**) {
+    auto ptr = *ctx + x;
 
     SkNx<N, SkPM4f> px;
     SkNf::Store4(tail ? (void*)&px : (void*)ptr, r,g,b,a);
@@ -532,15 +545,11 @@
 }
 
 
-STAGE(load_8888) {
-    auto ptr = *(const uint32_t**)ctx + x;
+STAGE_CTX(load_8888, const uint32_t**) {
+    auto ptr = *ctx + x;
     from_8888(load(tail, ptr), &r, &g, &b, &a);
 }
-STAGE(load_8888_d) {
-    auto ptr = *(const uint32_t**)ctx + x;
-    from_8888(load(tail, ptr), &dr, &dg, &db, &da);
-}
-STAGE(store_8888) {
+STAGE_CTX(store_8888, uint32_t**) {
     auto byte = [](const SkNf& x, int ix) {
         // Here's a neat trick: 0x47000000 == 32768.0f, and 0x470000ff == 32768.0f + (255/256.0f).
         auto v = SkNf_fma(255/256.0f, x, 32768.0f);
@@ -551,35 +560,33 @@
         return (SkNi::Load(&v) & 0xff) << (8*ix);  // B or G
     };
 
-    auto ptr = *(uint32_t**)ctx + x;
+    auto ptr = *ctx + x;
     store(tail, byte(r,0)|byte(g,1)|byte(b,2)|byte(a,3), (int*)ptr);
 }
 
-STAGE(load_tables) {
-    auto loadCtx = (const LoadTablesContext*)ctx;
-    auto ptr = loadCtx->fSrc + x;
+STAGE_CTX(load_tables, const LoadTablesContext*) {
+    auto ptr = ctx->fSrc + x;
 
     SkNu rgba = load(tail, ptr);
     auto to_int = [](const SkNu& v) { return SkNi::Load(&v); };
-    r = gather(tail, loadCtx->fR, to_int((rgba >>  0) & 0xff));
-    g = gather(tail, loadCtx->fG, to_int((rgba >>  8) & 0xff));
-    b = gather(tail, loadCtx->fB, to_int((rgba >> 16) & 0xff));
+    r = gather(tail, ctx->fR, to_int((rgba >>  0) & 0xff));
+    g = gather(tail, ctx->fG, to_int((rgba >>  8) & 0xff));
+    b = gather(tail, ctx->fB, to_int((rgba >> 16) & 0xff));
     a = SkNf_from_byte(rgba >> 24);
 }
 
-STAGE(store_tables) {
-    auto storeCtx = (const StoreTablesContext*)ctx;
-    auto ptr = storeCtx->fDst + x;
+STAGE_CTX(store_tables, const StoreTablesContext*) {
+    auto ptr = ctx->fDst + x;
 
-    float scale = storeCtx->fCount - 1;
+    float scale = ctx->fCount - 1;
     SkNi ri = SkNf_round(scale, r);
     SkNi gi = SkNf_round(scale, g);
     SkNi bi = SkNf_round(scale, b);
 
-    store(tail, ( SkNx_cast<int>(gather(tail, storeCtx->fR, ri)) << 0
-                | SkNx_cast<int>(gather(tail, storeCtx->fG, gi)) << 8
-                | SkNx_cast<int>(gather(tail, storeCtx->fB, bi)) << 16
-                | SkNf_round(255.0f, a)                          << 24), (int*)ptr);
+    store(tail, ( SkNx_cast<int>(gather(tail, ctx->fR, ri)) << 0
+                | SkNx_cast<int>(gather(tail, ctx->fG, gi)) << 8
+                | SkNx_cast<int>(gather(tail, ctx->fB, bi)) << 16
+                | SkNf_round(255.0f, a)                     << 24), (int*)ptr);
 }
 
 SI SkNf inv(const SkNf& x) { return 1.0f - x; }
@@ -640,16 +647,16 @@
     r = g = b = 0;
 }
 
-STAGE(matrix_2x3) {
-    auto m = (const float*)ctx;
+STAGE_CTX(matrix_2x3, const float*) {
+    auto m = ctx;
 
     auto R = SkNf_fma(r,m[0], SkNf_fma(g,m[2], m[4])),
          G = SkNf_fma(r,m[1], SkNf_fma(g,m[3], m[5]));
     r = R;
     g = G;
 }
-STAGE(matrix_3x4) {
-    auto m = (const float*)ctx;
+STAGE_CTX(matrix_3x4, const float*) {
+    auto m = ctx;
 
     auto R = SkNf_fma(r,m[0], SkNf_fma(g,m[3], SkNf_fma(b,m[6], m[ 9]))),
          G = SkNf_fma(r,m[1], SkNf_fma(g,m[4], SkNf_fma(b,m[7], m[10]))),
@@ -658,8 +665,8 @@
     g = G;
     b = B;
 }
-STAGE(matrix_4x5) {
-    auto m = (const float*)ctx;
+STAGE_CTX(matrix_4x5, const float*) {
+    auto m = ctx;
 
     auto R = SkNf_fma(r,m[0], SkNf_fma(g,m[4], SkNf_fma(b,m[ 8], SkNf_fma(a,m[12], m[16])))),
          G = SkNf_fma(r,m[1], SkNf_fma(g,m[5], SkNf_fma(b,m[ 9], SkNf_fma(a,m[13], m[17])))),
@@ -670,9 +677,9 @@
     b = B;
     a = A;
 }
-STAGE(matrix_perspective) {
+STAGE_CTX(matrix_perspective, const float*) {
     // N.B. unlike the matrix_NxM stages, this takes a row-major matrix.
-    auto m = (const float*)ctx;
+    auto m = ctx;
 
     auto R = SkNf_fma(r,m[0], SkNf_fma(g,m[1], m[2])),
          G = SkNf_fma(r,m[3], SkNf_fma(g,m[4], m[5])),
@@ -692,10 +699,10 @@
     // Max(NaN, 0) = 0, but Max(0, NaN) = NaN, so we want this exact order to ensure NaN => 0
     return SkNf::Min(SkNf::Max(SkNf::Load(result), 0.0f), 1.0f);
 }
-STAGE(parametric_r) { r = parametric(r, *(const SkColorSpaceTransferFn*)ctx); }
-STAGE(parametric_g) { g = parametric(g, *(const SkColorSpaceTransferFn*)ctx); }
-STAGE(parametric_b) { b = parametric(b, *(const SkColorSpaceTransferFn*)ctx); }
-STAGE(parametric_a) { a = parametric(a, *(const SkColorSpaceTransferFn*)ctx); }
+STAGE_CTX(parametric_r, const SkColorSpaceTransferFn*) { r = parametric(r, *ctx); }
+STAGE_CTX(parametric_g, const SkColorSpaceTransferFn*) { g = parametric(g, *ctx); }
+STAGE_CTX(parametric_b, const SkColorSpaceTransferFn*) { b = parametric(b, *ctx); }
+STAGE_CTX(parametric_a, const SkColorSpaceTransferFn*) { a = parametric(a, *ctx); }
 
 SI SkNf table(const SkNf& v, const SkTableTransferFn& table) {
     float result[N];
@@ -705,13 +712,13 @@
     // no need to clamp - tables are by-design [0,1] -> [0,1]
     return SkNf::Load(result);
 }
-STAGE(table_r) { r = table(r, *(const SkTableTransferFn*)ctx); }
-STAGE(table_g) { g = table(g, *(const SkTableTransferFn*)ctx); }
-STAGE(table_b) { b = table(b, *(const SkTableTransferFn*)ctx); }
-STAGE(table_a) { a = table(a, *(const SkTableTransferFn*)ctx); }
+STAGE_CTX(table_r, const SkTableTransferFn*) { r = table(r, *ctx); }
+STAGE_CTX(table_g, const SkTableTransferFn*) { g = table(g, *ctx); }
+STAGE_CTX(table_b, const SkTableTransferFn*) { b = table(b, *ctx); }
+STAGE_CTX(table_a, const SkTableTransferFn*) { a = table(a, *ctx); }
 
-STAGE(color_lookup_table) {
-    const SkColorLookUpTable* colorLUT = (const SkColorLookUpTable*)ctx;
+STAGE_CTX(color_lookup_table, const SkColorLookUpTable*) {
+    const SkColorLookUpTable* colorLUT = ctx;
     SkASSERT(3 == colorLUT->inputChannels() || 4 == colorLUT->inputChannels());
     SkASSERT(3 == colorLUT->outputChannels());
     float result[3][N];
@@ -782,33 +789,29 @@
     result = SkNf::Min(result, nextafterf(l, 0));
     return assert_in_tile(result, l);
 }
-STAGE( clamp_x) { r = clamp (r, *(const float*)ctx); }
-STAGE(repeat_x) { r = repeat(r, *(const float*)ctx); }
-STAGE(mirror_x) { r = mirror(r, *(const float*)ctx); }
-STAGE( clamp_y) { g = clamp (g, *(const float*)ctx); }
-STAGE(repeat_y) { g = repeat(g, *(const float*)ctx); }
-STAGE(mirror_y) { g = mirror(g, *(const float*)ctx); }
+STAGE_CTX( clamp_x, const float*) { r = clamp (r, *ctx); }
+STAGE_CTX(repeat_x, const float*) { r = repeat(r, *ctx); }
+STAGE_CTX(mirror_x, const float*) { r = mirror(r, *ctx); }
+STAGE_CTX( clamp_y, const float*) { g = clamp (g, *ctx); }
+STAGE_CTX(repeat_y, const float*) { g = repeat(g, *ctx); }
+STAGE_CTX(mirror_y, const float*) { g = mirror(g, *ctx); }
 
-STAGE(save_xy) {
-    auto sc = (SkImageShaderContext*)ctx;
-
-    r.store(sc->x);
-    g.store(sc->y);
+STAGE_CTX(save_xy, SkImageShaderContext*) {
+    r.store(ctx->x);
+    g.store(ctx->y);
 
     // Whether bilinear or bicubic, all sample points have the same fractional offset (fx,fy).
     // They're either the 4 corners of a logical 1x1 pixel or the 16 corners of a 3x3 grid
     // surrounding (x,y), all (0.5,0.5) off-center.
     auto fract = [](const SkNf& v) { return v - v.floor(); };
-    fract(r + 0.5f).store(sc->fx);
-    fract(g + 0.5f).store(sc->fy);
+    fract(r + 0.5f).store(ctx->fx);
+    fract(g + 0.5f).store(ctx->fy);
 }
 
-STAGE(accumulate) {
-    auto sc = (const SkImageShaderContext*)ctx;
-
+STAGE_CTX(accumulate, const SkImageShaderContext*) {
     // Bilinear and bicubic filtering are both separable, so we'll end up with independent
     // scale contributions in x and y that we multiply together to get each pixel's scale factor.
-    auto scale = SkNf::Load(sc->scalex) * SkNf::Load(sc->scaley);
+    auto scale = SkNf::Load(ctx->scalex) * SkNf::Load(ctx->scaley);
     dr = SkNf_fma(scale, r, dr);
     dg = SkNf_fma(scale, g, dg);
     db = SkNf_fma(scale, b, db);
@@ -820,25 +823,21 @@
 // At positive offsets, the x-axis contribution to that rectangular area is fx; (1-fx)
 // at negative x offsets.  The y-axis is treated symmetrically.
 template <int Scale>
-SI void bilinear_x(void* ctx, SkNf* x) {
-    auto sc = (SkImageShaderContext*)ctx;
-
-    *x = SkNf::Load(sc->x) + Scale*0.5f;
-    auto fx = SkNf::Load(sc->fx);
-    (Scale > 0 ? fx : (1.0f - fx)).store(sc->scalex);
+SI void bilinear_x(SkImageShaderContext* ctx, SkNf* x) {
+    *x = SkNf::Load(ctx->x) + Scale*0.5f;
+    auto fx = SkNf::Load(ctx->fx);
+    (Scale > 0 ? fx : (1.0f - fx)).store(ctx->scalex);
 }
 template <int Scale>
-SI void bilinear_y(void* ctx, SkNf* y) {
-    auto sc = (SkImageShaderContext*)ctx;
-
-    *y = SkNf::Load(sc->y) + Scale*0.5f;
-    auto fy = SkNf::Load(sc->fy);
-    (Scale > 0 ? fy : (1.0f - fy)).store(sc->scaley);
+SI void bilinear_y(SkImageShaderContext* ctx, SkNf* y) {
+    *y = SkNf::Load(ctx->y) + Scale*0.5f;
+    auto fy = SkNf::Load(ctx->fy);
+    (Scale > 0 ? fy : (1.0f - fy)).store(ctx->scaley);
 }
-STAGE(bilinear_nx) { bilinear_x<-1>(ctx, &r); }
-STAGE(bilinear_px) { bilinear_x<+1>(ctx, &r); }
-STAGE(bilinear_ny) { bilinear_y<-1>(ctx, &g); }
-STAGE(bilinear_py) { bilinear_y<+1>(ctx, &g); }
+STAGE_CTX(bilinear_nx, SkImageShaderContext*) { bilinear_x<-1>(ctx, &r); }
+STAGE_CTX(bilinear_px, SkImageShaderContext*) { bilinear_x<+1>(ctx, &r); }
+STAGE_CTX(bilinear_ny, SkImageShaderContext*) { bilinear_y<-1>(ctx, &g); }
+STAGE_CTX(bilinear_py, SkImageShaderContext*) { bilinear_y<+1>(ctx, &g); }
 
 
 // In bilinear interpolation, the 16 pixels at +/- 0.5 and +/- 1.5 offsets from the sample
@@ -859,94 +858,87 @@
 }
 
 template <int Scale>
-SI void bicubic_x(void* ctx, SkNf* x) {
-    auto sc = (SkImageShaderContext*)ctx;
-
-    *x = SkNf::Load(sc->x) + Scale*0.5f;
-    auto fx = SkNf::Load(sc->fx);
-    if (Scale == -3) { return bicubic_far (1.0f - fx).store(sc->scalex); }
-    if (Scale == -1) { return bicubic_near(1.0f - fx).store(sc->scalex); }
-    if (Scale == +1) { return bicubic_near(       fx).store(sc->scalex); }
-    if (Scale == +3) { return bicubic_far (       fx).store(sc->scalex); }
+SI void bicubic_x(SkImageShaderContext* ctx, SkNf* x) {
+    *x = SkNf::Load(ctx->x) + Scale*0.5f;
+    auto fx = SkNf::Load(ctx->fx);
+    if (Scale == -3) { return bicubic_far (1.0f - fx).store(ctx->scalex); }
+    if (Scale == -1) { return bicubic_near(1.0f - fx).store(ctx->scalex); }
+    if (Scale == +1) { return bicubic_near(       fx).store(ctx->scalex); }
+    if (Scale == +3) { return bicubic_far (       fx).store(ctx->scalex); }
     SkDEBUGFAIL("unreachable");
 }
 template <int Scale>
-SI void bicubic_y(void* ctx, SkNf* y) {
-    auto sc = (SkImageShaderContext*)ctx;
-
-    *y = SkNf::Load(sc->y) + Scale*0.5f;
-    auto fy = SkNf::Load(sc->fy);
-    if (Scale == -3) { return bicubic_far (1.0f - fy).store(sc->scaley); }
-    if (Scale == -1) { return bicubic_near(1.0f - fy).store(sc->scaley); }
-    if (Scale == +1) { return bicubic_near(       fy).store(sc->scaley); }
-    if (Scale == +3) { return bicubic_far (       fy).store(sc->scaley); }
+SI void bicubic_y(SkImageShaderContext* ctx, SkNf* y) {
+    *y = SkNf::Load(ctx->y) + Scale*0.5f;
+    auto fy = SkNf::Load(ctx->fy);
+    if (Scale == -3) { return bicubic_far (1.0f - fy).store(ctx->scaley); }
+    if (Scale == -1) { return bicubic_near(1.0f - fy).store(ctx->scaley); }
+    if (Scale == +1) { return bicubic_near(       fy).store(ctx->scaley); }
+    if (Scale == +3) { return bicubic_far (       fy).store(ctx->scaley); }
     SkDEBUGFAIL("unreachable");
 }
-STAGE(bicubic_n3x) { bicubic_x<-3>(ctx, &r); }
-STAGE(bicubic_n1x) { bicubic_x<-1>(ctx, &r); }
-STAGE(bicubic_p1x) { bicubic_x<+1>(ctx, &r); }
-STAGE(bicubic_p3x) { bicubic_x<+3>(ctx, &r); }
+STAGE_CTX(bicubic_n3x, SkImageShaderContext*) { bicubic_x<-3>(ctx, &r); }
+STAGE_CTX(bicubic_n1x, SkImageShaderContext*) { bicubic_x<-1>(ctx, &r); }
+STAGE_CTX(bicubic_p1x, SkImageShaderContext*) { bicubic_x<+1>(ctx, &r); }
+STAGE_CTX(bicubic_p3x, SkImageShaderContext*) { bicubic_x<+3>(ctx, &r); }
 
-STAGE(bicubic_n3y) { bicubic_y<-3>(ctx, &g); }
-STAGE(bicubic_n1y) { bicubic_y<-1>(ctx, &g); }
-STAGE(bicubic_p1y) { bicubic_y<+1>(ctx, &g); }
-STAGE(bicubic_p3y) { bicubic_y<+3>(ctx, &g); }
+STAGE_CTX(bicubic_n3y, SkImageShaderContext*) { bicubic_y<-3>(ctx, &g); }
+STAGE_CTX(bicubic_n1y, SkImageShaderContext*) { bicubic_y<-1>(ctx, &g); }
+STAGE_CTX(bicubic_p1y, SkImageShaderContext*) { bicubic_y<+1>(ctx, &g); }
+STAGE_CTX(bicubic_p3y, SkImageShaderContext*) { bicubic_y<+3>(ctx, &g); }
 
 
 template <typename T>
-SI SkNi offset_and_ptr(T** ptr, const void* ctx, const SkNf& x, const SkNf& y) {
-    auto sc = (const SkImageShaderContext*)ctx;
-
+SI SkNi offset_and_ptr(T** ptr, const SkImageShaderContext* ctx, const SkNf& x, const SkNf& y) {
     SkNi ix = SkNx_cast<int>(x),
          iy = SkNx_cast<int>(y);
-    SkNi offset = iy*sc->stride + ix;
+    SkNi offset = iy*ctx->stride + ix;
 
-    *ptr = (const T*)sc->pixels;
+    *ptr = (const T*)ctx->pixels;
     return offset;
 }
 
-STAGE(gather_a8) {
+STAGE_CTX(gather_a8, const SkImageShaderContext*) {
     const uint8_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     r = g = b = 0.0f;
     a = SkNf_from_byte(gather(tail, p, offset));
 }
-STAGE(gather_i8) {
-    auto sc = (const SkImageShaderContext*)ctx;
+STAGE_CTX(gather_i8, const SkImageShaderContext*) {
     const uint8_t* p;
-    SkNi offset = offset_and_ptr(&p, sc, r, g);
+    SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     SkNi ix = SkNx_cast<int>(gather(tail, p, offset));
-    from_8888(gather(tail, sc->ctable->readColors(), ix), &r, &g, &b, &a);
+    from_8888(gather(tail, ctx->ctable->readColors(), ix), &r, &g, &b, &a);
 }
-STAGE(gather_g8) {
+STAGE_CTX(gather_g8, const SkImageShaderContext*) {
     const uint8_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     r = g = b = SkNf_from_byte(gather(tail, p, offset));
     a = 1.0f;
 }
-STAGE(gather_565) {
+STAGE_CTX(gather_565, const SkImageShaderContext*) {
     const uint16_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     from_565(gather(tail, p, offset), &r, &g, &b);
     a = 1.0f;
 }
-STAGE(gather_4444) {
+STAGE_CTX(gather_4444, const SkImageShaderContext*) {
     const uint16_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     from_4444(gather(tail, p, offset), &r, &g, &b, &a);
 }
-STAGE(gather_8888) {
+STAGE_CTX(gather_8888, const SkImageShaderContext*) {
     const uint32_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
     from_8888(gather(tail, p, offset), &r, &g, &b, &a);
 }
-STAGE(gather_f16) {
+STAGE_CTX(gather_f16, const SkImageShaderContext*) {
     const uint64_t* p;
     SkNi offset = offset_and_ptr(&p, ctx, r, g);
 
@@ -966,40 +958,64 @@
 }
 
 namespace {
+
+    static void build_program(void** program, const SkRasterPipeline::Stage* stages, int nstages) {
+        for (int i = 0; i < nstages; i++) {
+            *program++ = (void*)enum_to_Fn(stages[i].stage);
+            if (stages[i].ctx) {
+                *program++ = stages[i].ctx;
+            }
+        }
+        *program++ = (void*)just_return;
+    }
+
+    static void run_program(void** program, size_t x, size_t y, size_t n) {
+        float dx[] = { 0,1,2,3,4,5,6,7 };
+        SkNf X = SkNf(x) + SkNf::Load(dx) + 0.5f,
+             Y = SkNf(y) + 0.5f,
+             _0 = SkNf(0),
+             _1 = SkNf(1);
+
+        auto start = (Fn)load_and_increment(&program);
+        while (n >= N) {
+            start(x*N, program, X,Y,_1,_0, _0,_0,_0,_0);
+            X += (float)N;
+            x += N;
+            n -= N;
+        }
+        if (n) {
+            start(x*N+n, program, X,Y,_1,_0, _0,_0,_0,_0);
+        }
+    }
+
+    // Compiled manages its memory manually because it's not safe to use
+    // std::vector, SkTDArray, etc without setting us up for big ODR violations.
     struct Compiled {
-        Compiled(const SkRasterPipeline::Stage* stages, int nstages) : fStages(nstages) {
-            if (nstages == 0) {
-                return;
+        Compiled(const SkRasterPipeline::Stage* stages, int nstages) {
+            int slots = nstages + 1;  // One extra for just_return.
+            for (int i = 0; i < nstages; i++) {
+                if (stages[i].ctx) {
+                    slots++;
+                }
             }
-            fStart = enum_to_Fn(stages[0].stage);
-            for (int i = 0; i < nstages-1; i++) {
-                fStages[i].next = enum_to_Fn(stages[i+1].stage);
-                fStages[i].ctx  = stages[i].ctx;
-            }
-            fStages[nstages-1].next = just_return;
-            fStages[nstages-1].ctx  = stages[nstages-1].ctx;
+            fProgram = (void**)sk_malloc_throw(slots * sizeof(void*));
+            build_program(fProgram, stages, nstages);
+        }
+        ~Compiled() { sk_free(fProgram); }
+
+        Compiled(const Compiled& o) {
+            int slots = 0;
+            while (o.fProgram[slots++] != (void*)just_return);
+
+            fProgram = (void**)sk_malloc_throw(slots * sizeof(void*));
+            memcpy(fProgram, o.fProgram, slots * sizeof(void*));
         }
 
         void operator()(size_t x, size_t y, size_t n) {
-            float dx[] = { 0,1,2,3,4,5,6,7 };
-            SkNf X = SkNf(x) + SkNf::Load(dx) + 0.5f,
-                 Y = SkNf(y) + 0.5f,
-                _0 = SkNf(0),
-                _1 = SkNf(1);
-
-            while (n >= N) {
-                fStart(fStages.data(), x*N, X,Y,_1,_0, _0,_0,_0,_0);
-                X += (float)N;
-                x += N;
-                n -= N;
-            }
-            if (n) {
-                fStart(fStages.data(), x*N+n, X,Y,_1,_0, _0,_0,_0,_0);
-            }
+            run_program(fProgram, x, y, n);
         }
 
-        Fn fStart = just_return;
-        std::vector<Stage> fStages;
+        void** fProgram;
     };
 }
 
@@ -1012,13 +1028,22 @@
 
     SI void run_pipeline(size_t x, size_t y, size_t n,
                          const SkRasterPipeline::Stage* stages, int nstages) {
-        Compiled{stages,nstages}(x,y,n);
+        static const int kStackMax = 256;
+        // Worst case is nstages stages with nstages context pointers, and just_return.
+        if (2*nstages+1 <= kStackMax) {
+            void* program[kStackMax];
+            build_program(program, stages, nstages);
+            run_program(program, x,y,n);
+        } else {
+            Compiled{stages,nstages}(x,y,n);
+        }
     }
 
 }  // namespace SK_OPTS_NS
 
 #undef SI
 #undef STAGE
+#undef STAGE_CTX
 #undef RGBA_XFERMODE
 #undef RGB_XFERMODE
 
diff --git a/src/pathops/SkOpCoincidence.cpp b/src/pathops/SkOpCoincidence.cpp
index 9f84111..f9ec157 100644
--- a/src/pathops/SkOpCoincidence.cpp
+++ b/src/pathops/SkOpCoincidence.cpp
@@ -996,7 +996,9 @@
         return true;
     }
     do {
-        SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast();
+        SkOpSpanBase* startSpan = coin->coinPtTStartWritable()->span();
+        FAIL_IF(!startSpan->upCastable());
+        SkOpSpan* start = startSpan->upCast();
         if (start->deleted()) {
             continue;
         }
diff --git a/src/pathops/SkPathOpsTSect.h b/src/pathops/SkPathOpsTSect.h
index 5d74c9a..4f2480d 100644
--- a/src/pathops/SkPathOpsTSect.h
+++ b/src/pathops/SkPathOpsTSect.h
@@ -2031,7 +2031,7 @@
     }
 
     bool matesWith(const SkClosestRecord& mate  SkDEBUGPARAMS(SkIntersections* i)) const {
-        SkASSERT(fC1Span == mate.fC1Span || fC1Span->endT() <= mate.fC1Span->startT()
+        SkOPOBJASSERT(i, fC1Span == mate.fC1Span || fC1Span->endT() <= mate.fC1Span->startT()
                 || mate.fC1Span->endT() <= fC1Span->startT());
         SkOPOBJASSERT(i, fC2Span == mate.fC2Span || fC2Span->endT() <= mate.fC2Span->startT()
                 || mate.fC2Span->endT() <= fC2Span->startT());
@@ -2281,8 +2281,12 @@
             if (!coincident->fCoinEnd.isMatch()) {
                 continue;
             }
+            double perpT = coincident->fCoinStart.perpT();
+            if (perpT < 0) {
+                return;
+            }
             int index = intersections->insertCoincident(coincident->fStartT,
-                    coincident->fCoinStart.perpT(), coincident->fPart[0]);
+                    perpT, coincident->fPart[0]);
             if ((intersections->insertCoincident(coincident->fEndT,
                     coincident->fCoinEnd.perpT(),
                     coincident->fPart[TCurve::kPointLast]) < 0) && index >= 0) {
diff --git a/src/pathops/SkPathOpsTightBounds.cpp b/src/pathops/SkPathOpsTightBounds.cpp
index d748ff5..f379c9e 100644
--- a/src/pathops/SkPathOpsTightBounds.cpp
+++ b/src/pathops/SkPathOpsTightBounds.cpp
@@ -75,6 +75,10 @@
     while ((current = current->next())) {
         bounds.add(current->bounds());
     }
+    if (scaleFactor > SK_Scalar1) {
+        bounds.set(bounds.left() * scaleFactor, bounds.top() * scaleFactor,
+                   bounds.right() * scaleFactor, bounds.bottom() * scaleFactor);
+    }
     *result = bounds;
     if (!moveBounds.isEmpty()) {
         result->join(moveBounds);
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 9ef3611..6da4d3b 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -1340,9 +1340,11 @@
         if (c.fUtf8Text) {  // real cluster
             // Check if `/ActualText` needed.
             const char* textPtr = c.fUtf8Text;
-            // TODO(halcanary): validate utf8 input.
-            SkUnichar unichar = SkUTF8_NextUnichar(&textPtr);
             const char* textEnd = c.fUtf8Text + c.fTextByteLength;
+            SkUnichar unichar = SkUTF8_NextUnicharWithError(&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
@@ -1353,7 +1355,10 @@
                 // the BOM marks this text as UTF-16BE, not PDFDocEncoding.
                 SkPDFUtils::WriteUTF16beHex(out, unichar);  // first char
                 while (textPtr < textEnd) {
-                    unichar = SkUTF8_NextUnichar(&textPtr);
+                    unichar = SkUTF8_NextUnicharWithError(&textPtr, textEnd);
+                    if (unichar < 0) {
+                        break;
+                    }
                     SkPDFUtils::WriteUTF16beHex(out, unichar);
                 }
                 out->writeText("> >> BDC\n");  // begin marked-content sequence
diff --git a/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h b/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h
index d7a53a9..4fc0072 100644
--- a/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h
+++ b/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h
@@ -12,7 +12,7 @@
 class SkBitSet;
 class SkGlyphCache;
 
-/* PDF 32000-1:2008, page 270: "The array’s elements have a variable
+/* 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,
diff --git a/src/pdf/SkPDFMetadata.cpp b/src/pdf/SkPDFMetadata.cpp
index 4206884..ee5c19b 100644
--- a/src/pdf/SkPDFMetadata.cpp
+++ b/src/pdf/SkPDFMetadata.cpp
@@ -164,7 +164,7 @@
         stream->write(streamBegin, strlen(streamBegin));
         // Do not compress this.  The standard requires that a
         // program that does not understand PDF can grep for
-        // "<?xpacket" and extracť the entire XML.
+        // "<?xpacket" and extract the entire XML.
         stream->write(fXML.c_str(), fXML.size());
         static const char streamEnd[] = "\nendstream";
         stream->write(streamEnd, strlen(streamEnd));
diff --git a/src/pipe/SkPipeCanvas.cpp b/src/pipe/SkPipeCanvas.cpp
index 33b58a4..e707f0d 100644
--- a/src/pipe/SkPipeCanvas.cpp
+++ b/src/pipe/SkPipeCanvas.cpp
@@ -5,15 +5,16 @@
  * found in the LICENSE file.
  */
 
-#include "SkPathEffect.h"
+#include "SkAutoMalloc.h"
 #include "SkColorFilter.h"
 #include "SkDrawLooper.h"
 #include "SkImageFilter.h"
 #include "SkMaskFilter.h"
+#include "SkPathEffect.h"
 #include "SkPipeCanvas.h"
 #include "SkPipeFormat.h"
-#include "SkRasterizer.h"
 #include "SkRSXform.h"
+#include "SkRasterizer.h"
 #include "SkShader.h"
 #include "SkStream.h"
 #include "SkTextBlob.h"
diff --git a/src/ports/SkFontConfigInterface_direct.cpp b/src/ports/SkFontConfigInterface_direct.cpp
index df68fae..c1a73e6 100644
--- a/src/ports/SkFontConfigInterface_direct.cpp
+++ b/src/ports/SkFontConfigInterface_direct.cpp
@@ -7,6 +7,7 @@
 
 /* migrated from chrome/src/skia/ext/SkFontHost_fontconfig_direct.cpp */
 
+#include "SkAutoMalloc.h"
 #include "SkBuffer.h"
 #include "SkDataTable.h"
 #include "SkFixed.h"
@@ -19,7 +20,6 @@
 #include "SkTDArray.h"
 #include "SkTemplates.h"
 #include "SkTypeface.h"
-#include "SkTypes.h"
 
 #include <fontconfig/fontconfig.h>
 #include <unistd.h>
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
index 15bc14d..47790f3 100644
--- a/src/ports/SkFontHost_FreeType.cpp
+++ b/src/ports/SkFontHost_FreeType.cpp
@@ -25,7 +25,6 @@
 #include "SkStream.h"
 #include "SkString.h"
 #include "SkTemplates.h"
-#include "SkTypes.h"
 #include <memory>
 
 #if defined(SK_CAN_USE_DLOPEN)
diff --git a/src/ports/SkFontHost_mac.cpp b/src/ports/SkFontHost_mac.cpp
index 65200e4..3e3887b 100644
--- a/src/ports/SkFontHost_mac.cpp
+++ b/src/ports/SkFontHost_mac.cpp
@@ -20,6 +20,7 @@
 #endif
 
 #include "SkAdvancedTypefaceMetrics.h"
+#include "SkAutoMalloc.h"
 #include "SkCGUtils.h"
 #include "SkColorPriv.h"
 #include "SkDescriptor.h"
@@ -780,7 +781,7 @@
     // Prior to 10.10, CTFontDrawGlyphs acted like CGContextShowGlyphsAtPositions and took
     // 'positions' which are in text space. The glyph location (in device space) must be
     // mapped into text space, so that CG can convert it back into device space.
-    // In 10.10.1, this is handled directly in CTFontDrawGlyphs.
+    // In 10.10.1, this is handled directly in CTFontDrawGlyphs.
     //
     // However, in 10.10.2 color glyphs no longer rotate based on the font transform.
     // So always make the font transform identity and place the transform on the context.
@@ -2379,6 +2380,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkFontMgr* SkFontMgr::Factory() { return new SkFontMgr_Mac; }
+sk_sp<SkFontMgr> SkFontMgr::Factory() { return sk_make_sp<SkFontMgr_Mac>(); }
 
 #endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
index 9229be6..e6e0715 100644
--- a/src/ports/SkFontHost_win.cpp
+++ b/src/ports/SkFontHost_win.cpp
@@ -2487,6 +2487,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkFontMgr* SkFontMgr_New_GDI() { return new SkFontMgrGDI; }
+sk_sp<SkFontMgr> SkFontMgr_New_GDI() { return sk_make_sp<SkFontMgrGDI>(); }
 
 #endif//defined(SK_BUILD_FOR_WIN32)
diff --git a/src/ports/SkFontMgr_FontConfigInterface.cpp b/src/ports/SkFontMgr_FontConfigInterface.cpp
index a883804..f5e3ef0 100644
--- a/src/ports/SkFontMgr_FontConfigInterface.cpp
+++ b/src/ports/SkFontMgr_FontConfigInterface.cpp
@@ -296,7 +296,7 @@
     }
 };
 
-SK_API SkFontMgr* SkFontMgr_New_FCI(sk_sp<SkFontConfigInterface> fci) {
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_FCI(sk_sp<SkFontConfigInterface> fci) {
     SkASSERT(fci);
-    return new SkFontMgr_FCI(std::move(fci));
+    return sk_make_sp<SkFontMgr_FCI>(std::move(fci));
 }
diff --git a/src/ports/SkFontMgr_FontConfigInterface_factory.cpp b/src/ports/SkFontMgr_FontConfigInterface_factory.cpp
index b2bb74e..a4ee138 100644
--- a/src/ports/SkFontMgr_FontConfigInterface_factory.cpp
+++ b/src/ports/SkFontMgr_FontConfigInterface_factory.cpp
@@ -9,7 +9,7 @@
 #include "SkFontMgr.h"
 #include "SkFontMgr_FontConfigInterface.h"
 
-SkFontMgr* SkFontMgr::Factory() {
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     sk_sp<SkFontConfigInterface> fci(SkFontConfigInterface::RefGlobal());
     if (!fci) {
         return nullptr;
diff --git a/src/ports/SkFontMgr_android.cpp b/src/ports/SkFontMgr_android.cpp
index 116bc01..d4d7967 100644
--- a/src/ports/SkFontMgr_android.cpp
+++ b/src/ports/SkFontMgr_android.cpp
@@ -535,7 +535,8 @@
     "OnlyCustom", "PreferCustom", "PreferSystem"
 };
 #endif
-SkFontMgr* SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts* custom) {
+
+sk_sp<SkFontMgr> SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts* custom) {
     if (custom) {
         SkASSERT(0 <= custom->fSystemFontUse);
         SkASSERT(custom->fSystemFontUse < SK_ARRAY_COUNT(gSystemFontUseStrings));
@@ -545,6 +546,5 @@
                   custom->fFontsXml,
                   custom->fFallbackFontsXml));
     }
-
-    return new SkFontMgr_Android(custom);
+    return sk_make_sp<SkFontMgr_Android>(custom);
 }
diff --git a/src/ports/SkFontMgr_android_factory.cpp b/src/ports/SkFontMgr_android_factory.cpp
index ce39b2c..88e0558 100644
--- a/src/ports/SkFontMgr_android_factory.cpp
+++ b/src/ports/SkFontMgr_android_factory.cpp
@@ -10,38 +10,7 @@
 #include "SkFontMgr.h"
 #include "SkFontMgr_android.h"
 
-// For test only.
-static const char* gTestFontsXml = nullptr;
-static const char* gTestFallbackFontsXml = nullptr;
-static const char* gTestBasePath = nullptr;
-
-void SkUseTestFontConfigFile(const char* fontsXml, const char* fallbackFontsXml,
-                             const char* basePath)
-{
-    gTestFontsXml = fontsXml;
-    gTestFallbackFontsXml = fallbackFontsXml;
-    gTestBasePath = basePath;
-    SkASSERT(gTestFontsXml);
-    SkASSERT(gTestFallbackFontsXml);
-    SkASSERT(gTestBasePath);
-    SkDEBUGF(("Test BasePath: %s Fonts: %s FallbackFonts: %s\n",
-              gTestBasePath, gTestFontsXml, gTestFallbackFontsXml));
-}
-
-SkFontMgr* SkFontMgr::Factory() {
-    // These globals exist so that Chromium can override the environment.
-    // TODO: these globals need to be removed, and Chromium use SkFontMgr_New_Android instead.
-    if ((gTestFontsXml || gTestFallbackFontsXml) && gTestBasePath) {
-        SkFontMgr_Android_CustomFonts custom = {
-            SkFontMgr_Android_CustomFonts::kOnlyCustom,
-            gTestBasePath,
-            gTestFontsXml,
-            gTestFallbackFontsXml,
-            false /* fIsolated */
-        };
-        return SkFontMgr_New_Android(&custom);
-    }
-
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     return SkFontMgr_New_Android(nullptr);
 }
 
diff --git a/src/ports/SkFontMgr_custom.cpp b/src/ports/SkFontMgr_custom.cpp
index ada7d0b..1b6baa1 100644
--- a/src/ports/SkFontMgr_custom.cpp
+++ b/src/ports/SkFontMgr_custom.cpp
@@ -420,8 +420,8 @@
     SkString fBaseDirectory;
 };
 
-SK_API SkFontMgr* SkFontMgr_New_Custom_Directory(const char* dir) {
-    return new SkFontMgr_Custom(DirectorySystemFontLoader(dir));
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_Custom_Directory(const char* dir) {
+    return sk_make_sp<SkFontMgr_Custom>(DirectorySystemFontLoader(dir));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -498,8 +498,8 @@
     const SkEmbeddedResourceHeader* fHeader;
 };
 
-SkFontMgr* SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header) {
-    return new SkFontMgr_Custom(EmbeddedSystemFontLoader(header));
+sk_sp<SkFontMgr> SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header) {
+    return sk_make_sp<SkFontMgr_Custom>(EmbeddedSystemFontLoader(header));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -518,6 +518,6 @@
 
 };
 
-SK_API SkFontMgr* SkFontMgr_New_Custom_Empty() {
-    return new SkFontMgr_Custom(EmptyFontLoader());
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_Custom_Empty() {
+    return sk_make_sp<SkFontMgr_Custom>(EmptyFontLoader());
 }
diff --git a/src/ports/SkFontMgr_custom_directory_factory.cpp b/src/ports/SkFontMgr_custom_directory_factory.cpp
index 0ca6f4b..7ed4711 100644
--- a/src/ports/SkFontMgr_custom_directory_factory.cpp
+++ b/src/ports/SkFontMgr_custom_directory_factory.cpp
@@ -12,6 +12,6 @@
 #    define SK_FONT_FILE_PREFIX "/usr/share/fonts/"
 #endif
 
-SkFontMgr* SkFontMgr::Factory() {
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     return SkFontMgr_New_Custom_Directory(SK_FONT_FILE_PREFIX);
 }
diff --git a/src/ports/SkFontMgr_custom_embedded_factory.cpp b/src/ports/SkFontMgr_custom_embedded_factory.cpp
index 6ea6a2d..7a6df43 100644
--- a/src/ports/SkFontMgr_custom_embedded_factory.cpp
+++ b/src/ports/SkFontMgr_custom_embedded_factory.cpp
@@ -9,9 +9,9 @@
 
 struct SkEmbeddedResource { const uint8_t* data; size_t size; };
 struct SkEmbeddedResourceHeader { const SkEmbeddedResource* entries; int count; };
-SkFontMgr* SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header);
+sk_sp<SkFontMgr> SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header);
 
 extern "C" const SkEmbeddedResourceHeader SK_EMBEDDED_FONTS;
-SkFontMgr* SkFontMgr::Factory() {
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     return SkFontMgr_New_Custom_Embedded(&SK_EMBEDDED_FONTS);
 }
diff --git a/src/ports/SkFontMgr_custom_empty_factory.cpp b/src/ports/SkFontMgr_custom_empty_factory.cpp
index c9487cd..bc988f4 100644
--- a/src/ports/SkFontMgr_custom_empty_factory.cpp
+++ b/src/ports/SkFontMgr_custom_empty_factory.cpp
@@ -8,6 +8,6 @@
 #include "SkFontMgr.h"
 #include "SkFontMgr_custom.h"
 
-SkFontMgr* SkFontMgr::Factory() {
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     return SkFontMgr_New_Custom_Empty();
 }
diff --git a/src/ports/SkFontMgr_empty_factory.cpp b/src/ports/SkFontMgr_empty_factory.cpp
index b4232cd..9ca9e65 100644
--- a/src/ports/SkFontMgr_empty_factory.cpp
+++ b/src/ports/SkFontMgr_empty_factory.cpp
@@ -7,7 +7,7 @@
 
 #include "SkFontMgr.h"
 
-SkFontMgr* SkFontMgr::Factory() {
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     // Always return nullptr, an empty SkFontMgr will be used.
     return nullptr;
 }
diff --git a/src/ports/SkFontMgr_fontconfig.cpp b/src/ports/SkFontMgr_fontconfig.cpp
index 3592fec..0fe352a 100644
--- a/src/ports/SkFontMgr_fontconfig.cpp
+++ b/src/ports/SkFontMgr_fontconfig.cpp
@@ -953,6 +953,6 @@
     }
 };
 
-SK_API SkFontMgr* SkFontMgr_New_FontConfig(FcConfig* fc) {
-    return new SkFontMgr_fontconfig(fc);
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_FontConfig(FcConfig* fc) {
+    return sk_make_sp<SkFontMgr_fontconfig>(fc);
 }
diff --git a/src/ports/SkFontMgr_fontconfig_factory.cpp b/src/ports/SkFontMgr_fontconfig_factory.cpp
index cdf0556..2f5ffb4 100644
--- a/src/ports/SkFontMgr_fontconfig_factory.cpp
+++ b/src/ports/SkFontMgr_fontconfig_factory.cpp
@@ -9,6 +9,6 @@
 #include "SkFontMgr_fontconfig.h"
 #include "SkTypes.h"
 
-SkFontMgr* SkFontMgr::Factory() {
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     return SkFontMgr_New_FontConfig(nullptr);
 }
diff --git a/src/ports/SkFontMgr_win_dw.cpp b/src/ports/SkFontMgr_win_dw.cpp
index 547cbe3..905f413 100644
--- a/src/ports/SkFontMgr_win_dw.cpp
+++ b/src/ports/SkFontMgr_win_dw.cpp
@@ -1043,14 +1043,14 @@
 ////////////////////////////////////////////////////////////////////////////////
 #include "SkTypeface_win.h"
 
-SK_API SkFontMgr* SkFontMgr_New_DirectWrite(IDWriteFactory* factory,
-                                            IDWriteFontCollection* collection) {
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_DirectWrite(IDWriteFactory* factory,
+                                                  IDWriteFontCollection* collection) {
     return SkFontMgr_New_DirectWrite(factory, collection, nullptr);
 }
 
-SK_API SkFontMgr* SkFontMgr_New_DirectWrite(IDWriteFactory* factory,
-                                            IDWriteFontCollection* collection,
-                                            IDWriteFontFallback* fallback) {
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_DirectWrite(IDWriteFactory* factory,
+                                                  IDWriteFontCollection* collection,
+                                                  IDWriteFontFallback* fallback) {
     if (nullptr == factory) {
         factory = sk_get_dwrite_factory();
         if (nullptr == factory) {
@@ -1081,15 +1081,16 @@
         };
     }
 
-    return new SkFontMgr_DirectWrite(factory, collection, fallback, localeName, localeNameLen);
+    return sk_make_sp<SkFontMgr_DirectWrite>(factory, collection, fallback,
+                                             localeName, localeNameLen);
 }
 
 #include "SkFontMgr_indirect.h"
-SK_API SkFontMgr* SkFontMgr_New_DirectWriteRenderer(sk_sp<SkRemotableFontMgr> proxy) {
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_DirectWriteRenderer(sk_sp<SkRemotableFontMgr> proxy) {
     sk_sp<SkFontMgr> impl(SkFontMgr_New_DirectWrite());
     if (!impl) {
         return nullptr;
     }
-    return new SkFontMgr_Indirect(std::move(impl), std::move(proxy));
+    return sk_make_sp<SkFontMgr_Indirect>(std::move(impl), std::move(proxy));
 }
 #endif//defined(SK_BUILD_FOR_WIN32)
diff --git a/src/ports/SkFontMgr_win_dw_factory.cpp b/src/ports/SkFontMgr_win_dw_factory.cpp
index 52e22ae..464a05b 100644
--- a/src/ports/SkFontMgr_win_dw_factory.cpp
+++ b/src/ports/SkFontMgr_win_dw_factory.cpp
@@ -11,7 +11,7 @@
 #include "SkFontMgr.h"
 #include "SkTypeface_win.h"
 
-SkFontMgr* SkFontMgr::Factory() {
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     return SkFontMgr_New_DirectWrite();
 }
 
diff --git a/src/ports/SkFontMgr_win_gdi_factory.cpp b/src/ports/SkFontMgr_win_gdi_factory.cpp
index c1ca822..6015794 100644
--- a/src/ports/SkFontMgr_win_gdi_factory.cpp
+++ b/src/ports/SkFontMgr_win_gdi_factory.cpp
@@ -11,7 +11,7 @@
 #include "SkFontMgr.h"
 #include "SkTypeface_win.h"
 
-SkFontMgr* SkFontMgr::Factory() {
+sk_sp<SkFontMgr> SkFontMgr::Factory() {
     return SkFontMgr_New_GDI();
 }
 
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index 0a4e8f6..abf318a 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -108,6 +108,7 @@
 
     // ImageFilter
     SkImageFilter::InitializeFlattenables();
+    SkArithmeticImageFilter::InitializeFlattenables();
     SkXfermodeImageFilter::InitializeFlattenables();
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDilateImageFilter)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDisplacementMapEffect)
diff --git a/src/ports/SkImageEncoder_WIC.cpp b/src/ports/SkImageEncoder_WIC.cpp
index 52f4599..6bc8b54 100644
--- a/src/ports/SkImageEncoder_WIC.cpp
+++ b/src/ports/SkImageEncoder_WIC.cpp
@@ -28,14 +28,16 @@
 #undef INT64_MAX
 #undef UINT64_MAX
 
-#include <wincodec.h>
 #include "SkAutoCoInitialize.h"
+#include "SkAutoMalloc.h"
 #include "SkBitmap.h"
 #include "SkImageEncoderPriv.h"
 #include "SkIStream.h"
+#include "SkImageEncoder.h"
 #include "SkStream.h"
 #include "SkTScopedComPtr.h"
 #include "SkUnPreMultiply.h"
+#include <wincodec.h>
 
 //All Windows SDKs back to XPSP2 export the CLSID_WICImagingFactory symbol.
 //In the Windows8 SDK the CLSID_WICImagingFactory symbol is still exported
diff --git a/src/sfnt/SkOTTable_name.cpp b/src/sfnt/SkOTTable_name.cpp
index 476e0ce..9670b6f 100644
--- a/src/sfnt/SkOTTable_name.cpp
+++ b/src/sfnt/SkOTTable_name.cpp
@@ -46,7 +46,7 @@
  *  In MacRoman the first 128 code points match ASCII code points.
  *  This maps the second 128 MacRoman code points to unicode code points.
  */
-static uint16_t UnicodeFromMacRoman[0x80] = {
+static const uint16_t UnicodeFromMacRoman[0x80] = {
     0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1,
     0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,
     0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3,
@@ -73,7 +73,7 @@
     }
 }
 
-static struct BCP47FromLanguageId {
+static const struct BCP47FromLanguageId {
     uint16_t languageID;
     const char* bcp47;
 }
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 0698817..992db6e 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -876,7 +876,12 @@
             case Token::PLUS:       return RESULT(Int,  +);
             case Token::MINUS:      return RESULT(Int,  -);
             case Token::STAR:       return RESULT(Int,  *);
-            case Token::SLASH:      return RESULT(Int,  /);
+            case Token::SLASH:
+                if (rightVal) {
+                    return RESULT(Int, /);
+                }
+                fErrors.error(right.fPosition, "division by zero");
+                return nullptr;
             case Token::PERCENT:    return RESULT(Int,  %);
             case Token::BITWISEAND: return RESULT(Int,  &);
             case Token::BITWISEOR:  return RESULT(Int,  |);
@@ -900,7 +905,12 @@
             case Token::PLUS:       return RESULT(Float, +);
             case Token::MINUS:      return RESULT(Float, -);
             case Token::STAR:       return RESULT(Float, *);
-            case Token::SLASH:      return RESULT(Float, /);
+            case Token::SLASH:
+                if (rightVal) {
+                    return RESULT(Float, /);
+                }
+                fErrors.error(right.fPosition, "division by zero");
+                return nullptr;
             case Token::EQEQ:       return RESULT(Bool,  ==);
             case Token::NEQ:        return RESULT(Bool,  !=);
             case Token::GT:         return RESULT(Bool,  >);
@@ -974,15 +984,20 @@
     const Type* falseType;
     const Type* resultType;
     if (!determine_binary_type(fContext, Token::EQEQ, ifTrue->fType, ifFalse->fType, &trueType,
-                               &falseType, &resultType, true)) {
+                               &falseType, &resultType, true) || trueType != falseType) {
         fErrors.error(expression.fPosition, "ternary operator result mismatch: '" +
                                             ifTrue->fType.fName + "', '" +
                                             ifFalse->fType.fName + "'");
         return nullptr;
     }
-    ASSERT(trueType == falseType);
     ifTrue = this->coerce(std::move(ifTrue), *trueType);
+    if (!ifTrue) {
+        return nullptr;
+    }
     ifFalse = this->coerce(std::move(ifFalse), *falseType);
+    if (!ifFalse) {
+        return nullptr;
+    }
     if (test->fKind == Expression::kBoolLiteral_Kind) {
         // static boolean test, just return one of the branches
         if (((BoolLiteral&) *test).fValue) {
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index 63fe020..1e85f11 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -1879,12 +1879,15 @@
                                result, 0, out);
         IntLiteral fieldIndex(fContext, Position(), fRTHeightFieldIndex);
         SpvId fieldIndexId = this->writeIntLiteral(fieldIndex);
-        SpvId heightRead = this->nextId();
+        SpvId heightPtr = this->nextId();
         this->writeOpCode(SpvOpAccessChain, 5, out);
         this->writeWord(this->getPointerType(*fContext.fFloat_Type, SpvStorageClassUniform), out);
-        this->writeWord(heightRead, out);
+        this->writeWord(heightPtr, out);
         this->writeWord(fRTHeightStructId, out);
         this->writeWord(fieldIndexId, out);
+        SpvId heightRead = this->nextId();
+        this->writeInstruction(SpvOpLoad, this->getType(*fContext.fFloat_Type), heightRead,
+                               heightPtr, out);
         SpvId rawYId = this->nextId();
         this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fFloat_Type), rawYId,
                                result, 1, out);
diff --git a/src/sksl/SkSLUtil.cpp b/src/sksl/SkSLUtil.cpp
index 98e5fa4..e93a953 100644
--- a/src/sksl/SkSLUtil.cpp
+++ b/src/sksl/SkSLUtil.cpp
@@ -11,6 +11,9 @@
 #define __STDC_FORMAT_MACROS
 #endif
 #include <cinttypes>
+#include <locale>
+#include <sstream>
+#include <string>
 
 namespace SkSL {
 
@@ -60,7 +63,12 @@
 }
 
 double stod(SkString s) {
-    return atof(s.c_str());
+    double result;
+    std::string str(s.c_str(), s.size());
+    std::stringstream buffer(str);
+    buffer.imbue(std::locale::classic());
+    buffer >> result;
+    return result;
 }
 
 long stol(SkString s) {
diff --git a/src/splicer/SkSplicer.cpp b/src/splicer/SkSplicer.cpp
new file mode 100644
index 0000000..b274553
--- /dev/null
+++ b/src/splicer/SkSplicer.cpp
@@ -0,0 +1,323 @@
+/*
+ * 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 "SkCpu.h"
+#include "SkOpts.h"
+#include "SkRasterPipeline.h"
+#include "SkStream.h"
+#if defined(_MSC_VER)
+    #include <windows.h>
+#else
+    #include <sys/mman.h>
+#endif
+
+#include "SkSplicer_generated.h"
+#include "SkSplicer_shared.h"
+
+// Uncomment to dump output JIT'd pipeline.
+//#define DUMP "/tmp/dump.bin"
+//
+// On x86, we'll include IACA markers too.
+//   https://software.intel.com/en-us/articles/intel-architecture-code-analyzer
+// Running IACA will disassemble, and more.
+//   $ ./iaca.sh -arch HSW -64 -mark 0 /tmp/dump.bin | less
+//
+// To disassemble an aarch64 dump,
+//   $ gobjdump -b binary -m aarch64 -D dump.bin
+
+namespace {
+
+    // Stages expect these constants to be set to these values.
+    // It's fine to rearrange and add new ones if you update SkSplicer_constants.
+    static const SkSplicer_constants kConstants = {
+        0x000000ff, 1.0f, 255.0f, 1/255.0f,
+        0.0025f, 0.6975f, 0.3000f, 1/12.92f, 0.055f,       // from_srgb
+        12.46f, 0.411192f, 0.689206f, -0.0988f, 0.0043f,   //   to_srgb
+    };
+
+    // We do this a lot, so it's nice to infer the correct size.  Works fine with arrays.
+    template <typename T>
+    static void splice(SkWStream* buf, const T& val) {
+        buf->write(&val, sizeof(val));
+    }
+
+#if defined(__aarch64__)
+    static constexpr int kStride = 4;
+    static void set_ctx(SkWStream* buf, void* ctx) {
+        uint16_t parts[4];
+        memcpy(parts, &ctx, 8);
+        splice(buf, 0xd2f00000 | (parts[3] << 5) | 0x2);  // move  16-bit intermediate << 48 into x2
+        splice(buf, 0xf2c00000 | (parts[2] << 5) | 0x2);  // merge 16-bit intermediate << 32 into x2
+        splice(buf, 0xf2a00000 | (parts[1] << 5) | 0x2);  // merge 16-bit intermediate << 16 into x2
+        splice(buf, 0xf2800000 | (parts[0] << 5) | 0x2);  // merge 16-bit intermediate <<  0 into x2
+    }
+    static void loop(SkWStream* buf, int loop_start) {
+        splice(buf, 0x91001000);        // add x0, x0, #4
+        splice(buf, 0xeb01001f);        // cmp x0, x1
+        int off = loop_start - (int)buf->bytesWritten();
+        off /= 4;   // bytes -> instructions, still signed
+        off = (off & 0x7ffff) << 5;  // 19 bit maximum range (+- 256K instructions)
+        splice(buf, 0x54000003 | off); // b.cc loop_start  (cc == "carry clear", unsigned less than)
+    }
+    static void ret(SkWStream* buf) {
+        splice(buf, 0xd65f03c0);  // ret
+    }
+#else
+    static constexpr int kStride = 8;
+    static void set_ctx(SkWStream* buf, void* ctx) {
+        static const uint8_t movabsq_rdx[] = { 0x48, 0xba };
+        splice(buf, movabsq_rdx);  // movabsq <next 8 bytes>, %rdx
+        splice(buf, ctx);
+    }
+    static void loop(SkWStream* buf, int loop_start) {
+        static const uint8_t  addq_8_rdi[] = { 0x48, 0x83, 0xc7, 0x08 };
+        static const uint8_t cmp_rsi_rdi[] = { 0x48, 0x39, 0xf7 };
+        static const uint8_t     jb_near[] = { 0x0f, 0x8c };
+        splice(buf, addq_8_rdi);   // addq $8, %rdi
+        splice(buf, cmp_rsi_rdi);  // cmp %rsi, %rdi
+        splice(buf, jb_near);      // jb <next 4 bytes>  (b == "before", unsigned less than)
+        splice(buf, loop_start - (int)(buf->bytesWritten() + 4));
+    }
+    static void ret(SkWStream* buf) {
+        static const uint8_t vzeroupper[] = { 0xc5, 0xf8, 0x77 };
+        static const uint8_t        ret[] = { 0xc3 };
+        splice(buf, vzeroupper);
+        splice(buf, ret);
+    }
+#endif
+
+#if defined(_MSC_VER)
+    // Adapt from MS ABI to System V ABI used by stages.
+    static void before_loop(SkWStream* buf) {
+        static const uint8_t ms_to_system_v[] = {
+            0x56,                                         // push   %rsi
+            0x57,                                         // push   %rdi
+            0x48,0x81,0xec,0xa8,0x00,0x00,0x00,           // sub    $0xa8,%rsp
+            0xc5,0x78,0x29,0xbc,0x24,0x90,0x00,0x00,0x00, // vmovaps %xmm15,0x90(%rsp)
+            0xc5,0x78,0x29,0xb4,0x24,0x80,0x00,0x00,0x00, // vmovaps %xmm14,0x80(%rsp)
+            0xc5,0x78,0x29,0x6c,0x24,0x70,                // vmovaps %xmm13,0x70(%rsp)
+            0xc5,0x78,0x29,0x64,0x24,0x60,                // vmovaps %xmm12,0x60(%rsp)
+            0xc5,0x78,0x29,0x5c,0x24,0x50,                // vmovaps %xmm11,0x50(%rsp)
+            0xc5,0x78,0x29,0x54,0x24,0x40,                // vmovaps %xmm10,0x40(%rsp)
+            0xc5,0x78,0x29,0x4c,0x24,0x30,                // vmovaps %xmm9,0x30(%rsp)
+            0xc5,0x78,0x29,0x44,0x24,0x20,                // vmovaps %xmm8,0x20(%rsp)
+            0xc5,0xf8,0x29,0x7c,0x24,0x10,                // vmovaps %xmm7,0x10(%rsp)
+            0xc5,0xf8,0x29,0x34,0x24,                     // vmovaps %xmm6,(%rsp)
+            0x48,0x89,0xcf,                               // mov    %rcx,%rdi
+            0x48,0x89,0xd6,                               // mov    %rdx,%rsi
+            0x4c,0x89,0xc2,                               // mov    %r8,%rdx
+            0x4c,0x89,0xc9,                               // mov    %r9,%rcx
+        };
+        splice(buf, ms_to_system_v);
+    }
+    static void after_loop(SkWStream* buf) {
+        static const uint8_t system_v_to_ms[] = {
+            0xc5,0xf8,0x28,0x34,0x24,                     // vmovaps (%rsp),%xmm6
+            0xc5,0xf8,0x28,0x7c,0x24,0x10,                // vmovaps 0x10(%rsp),%xmm7
+            0xc5,0x78,0x28,0x44,0x24,0x20,                // vmovaps 0x20(%rsp),%xmm8
+            0xc5,0x78,0x28,0x4c,0x24,0x30,                // vmovaps 0x30(%rsp),%xmm9
+            0xc5,0x78,0x28,0x54,0x24,0x40,                // vmovaps 0x40(%rsp),%xmm10
+            0xc5,0x78,0x28,0x5c,0x24,0x50,                // vmovaps 0x50(%rsp),%xmm11
+            0xc5,0x78,0x28,0x64,0x24,0x60,                // vmovaps 0x60(%rsp),%xmm12
+            0xc5,0x78,0x28,0x6c,0x24,0x70,                // vmovaps 0x70(%rsp),%xmm13
+            0xc5,0x78,0x28,0xb4,0x24,0x80,0x00,0x00,0x00, // vmovaps 0x80(%rsp),%xmm14
+            0xc5,0x78,0x28,0xbc,0x24,0x90,0x00,0x00,0x00, // vmovaps 0x90(%rsp),%xmm15
+            0x48,0x81,0xc4,0xa8,0x00,0x00,0x00,           // add    $0xa8,%rsp
+            0x5f,                                         // pop    %rdi
+            0x5e,                                         // pop    %rsi
+        };
+        splice(buf, system_v_to_ms);
+    }
+#elif !defined(__aarch64__) && defined(DUMP)
+    // IACA start and end markers.
+    static const uint8_t      ud2[] = { 0x0f, 0x0b };         // undefined... crashes when run
+    static const uint8_t     nop3[] = { 0x64, 0x67, 0x90 };   // 3 byte no-op
+    static const uint8_t movl_ebx[] = { 0xbb };               // move next 4 bytes into ebx
+
+    static void before_loop(SkWStream* buf) {
+        splice(buf, ud2);
+        splice(buf, movl_ebx);
+        splice(buf, 111);
+        splice(buf, nop3);
+    }
+    static void after_loop(SkWStream* buf) {
+        splice(buf, movl_ebx);
+        splice(buf, 222);
+        splice(buf, nop3);
+        splice(buf, ud2);
+    }
+#else
+    static void before_loop(SkWStream*) {}
+    static void after_loop (SkWStream*) {}
+#endif
+
+    // We can only mprotect / VirtualProtect at 4K page granularity.
+    static size_t round_up_to_full_pages(size_t len) {
+        size_t size = 0;
+        while (size < len) {
+            size += 4096;
+        }
+        return size;
+    }
+
+#if defined(_MSC_VER)
+    // Copy len bytes from src to memory that's executable.  cleanup with cleanup_executable_mem().
+    static void* copy_to_executable_mem(const void* src, size_t* len) {
+        if (!src || !*len) {
+            return nullptr;
+        }
+
+        size_t alloc = round_up_to_full_pages(*len);
+
+        auto fn = VirtualAlloc(nullptr, alloc, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
+        memcpy(fn, src, *len);
+
+        DWORD dont_care;
+        VirtualProtect(fn, alloc, PAGE_EXECUTE_READ, &dont_care);
+
+        *len = alloc;
+        return fn;
+    }
+    static void cleanup_executable_mem(void* fn, size_t len) {
+        if (fn) {
+            VirtualFree(fn, 0, MEM_RELEASE);
+        }
+    }
+#else
+    static void* copy_to_executable_mem(const void* src, size_t* len) {
+        if (!src || !*len) {
+            return nullptr;
+        }
+
+        size_t alloc = round_up_to_full_pages(*len);
+
+        auto fn = mmap(nullptr, alloc, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
+        memcpy(fn, src, *len);
+
+        mprotect(fn, alloc, PROT_READ|PROT_EXEC);
+        __builtin___clear_cache((char*)fn, (char*)fn + *len);  // Essential on ARM; no-op on x86.
+
+        *len = alloc;
+        return fn;
+    }
+    static void cleanup_executable_mem(void* fn, size_t len) {
+        if (fn) {
+            munmap(fn, len);
+        }
+    }
+#endif
+
+    struct Spliced {
+
+        Spliced(const SkRasterPipeline::Stage* stages, int nstages) {
+            // We always create a backup interpreter pipeline,
+            //   - to handle any program we can't, and
+            //   - to handle the n < kStride tails.
+            fBackup     = SkOpts::compile_pipeline(stages, nstages);
+            fSplicedLen = 0;
+            fSpliced    = nullptr;
+            // If we return early anywhere in here, !fSpliced means we'll use fBackup instead.
+
+        #if !defined(__aarch64__)
+            // To keep things simple, only one target supported: Haswell+ x86-64.
+            if (!SkCpu::Supports(SkCpu::HSW) || sizeof(void*) != 8) {
+                return;
+            }
+        #endif
+
+            SkDynamicMemoryWStream buf;
+
+            // Our loop is the equivalent of this C++ code:
+            //    do {
+            //        ... run spliced stages...
+            //        x += kStride;
+            //    } while(x < limit);
+            before_loop(&buf);
+            auto loop_start = buf.bytesWritten();  // Think of this like a label, loop_start:
+
+            for (int i = 0; i < nstages; i++) {
+                // If a stage has a context pointer, load it into rdx/x2, Stage argument 3 "ctx".
+                if (stages[i].ctx) {
+                    set_ctx(&buf, stages[i].ctx);
+                }
+
+                // Splice in the code for the Stages, generated offline into SkSplicer_generated.h.
+                switch(stages[i].stage) {
+                    case SkRasterPipeline::clear:        splice(&buf, kSplice_clear       ); break;
+                    case SkRasterPipeline::plus_:        splice(&buf, kSplice_plus        ); break;
+                    case SkRasterPipeline::srcover:      splice(&buf, kSplice_srcover     ); break;
+                    case SkRasterPipeline::dstover:      splice(&buf, kSplice_dstover     ); break;
+                    case SkRasterPipeline::clamp_0:      splice(&buf, kSplice_clamp_0     ); break;
+                    case SkRasterPipeline::clamp_1:      splice(&buf, kSplice_clamp_1     ); break;
+                    case SkRasterPipeline::clamp_a:      splice(&buf, kSplice_clamp_a     ); break;
+                    case SkRasterPipeline::swap:         splice(&buf, kSplice_swap        ); break;
+                    case SkRasterPipeline::move_src_dst: splice(&buf, kSplice_move_src_dst); break;
+                    case SkRasterPipeline::move_dst_src: splice(&buf, kSplice_move_dst_src); break;
+                    case SkRasterPipeline::premul:       splice(&buf, kSplice_premul      ); break;
+                    case SkRasterPipeline::unpremul:     splice(&buf, kSplice_unpremul    ); break;
+                    case SkRasterPipeline::from_srgb:    splice(&buf, kSplice_from_srgb   ); break;
+                    case SkRasterPipeline::to_srgb:      splice(&buf, kSplice_to_srgb     ); break;
+                    case SkRasterPipeline::scale_u8:     splice(&buf, kSplice_scale_u8    ); break;
+                    case SkRasterPipeline::load_8888:    splice(&buf, kSplice_load_8888   ); break;
+                    case SkRasterPipeline::store_8888:   splice(&buf, kSplice_store_8888  ); break;
+                    case SkRasterPipeline::load_f16:     splice(&buf, kSplice_load_f16    ); break;
+                    case SkRasterPipeline::store_f16:    splice(&buf, kSplice_store_f16   ); break;
+
+                    // No joy (probably just not yet implemented).
+                    default:
+                        //SkDebugf("SkSplicer can't yet handle stage %d.\n", stages[i].stage);
+                        return;
+                }
+            }
+
+            loop(&buf, loop_start);  // Loop back to handle more pixels if not done.
+            after_loop(&buf);
+            ret(&buf);  // We're done.
+
+            auto data = buf.detachAsData();
+            fSplicedLen = data->size();
+            fSpliced    = copy_to_executable_mem(data->data(), &fSplicedLen);
+
+        #if defined(DUMP)
+            SkFILEWStream(DUMP).write(data->data(), data->size());
+        #endif
+        }
+
+        // Spliced is stored in a std::function, so it needs to be copyable.
+        Spliced(const Spliced& o) : fBackup    (o.fBackup)
+                                  , fSplicedLen(o.fSplicedLen)
+                                  , fSpliced   (copy_to_executable_mem(o.fSpliced, &fSplicedLen)) {}
+
+        ~Spliced() {
+            cleanup_executable_mem(fSpliced, fSplicedLen);
+        }
+
+        // Here's where we call fSpliced if we created it, fBackup if not.
+        void operator()(size_t x, size_t y, size_t n) const {
+            size_t body = n/kStride*kStride;   // Largest multiple of kStride (4 or 8) <= n.
+            if (fSpliced && body) {            // Can we run fSpliced for at least one kStride?
+                // TODO: At some point we will want to pass in y...
+                using Fn = void(size_t x, size_t limit, void* ctx, const SkSplicer_constants* k);
+                ((Fn*)fSpliced)(x, x+body, nullptr, &kConstants);
+
+                // Fall through to fBackup for any n<kStride last pixels.
+                x += body;
+                n -= body;
+            }
+            fBackup(x,y,n);
+        }
+
+        std::function<void(size_t, size_t, size_t)> fBackup;
+        size_t                                      fSplicedLen;
+        void*                                       fSpliced;
+    };
+
+}
+
+std::function<void(size_t, size_t, size_t)> SkRasterPipeline::jit() const {
+    return Spliced(fStages.data(), SkToInt(fStages.size()));
+}
diff --git a/src/splicer/SkSplicer_generated.h b/src/splicer/SkSplicer_generated.h
new file mode 100644
index 0000000..7baf782
--- /dev/null
+++ b/src/splicer/SkSplicer_generated.h
@@ -0,0 +1,534 @@
+/*
+ * 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 SkSplicer_generated_DEFINED
+#define SkSplicer_generated_DEFINED
+
+// This file is generated semi-automatically with this command:
+//   $ src/splicer/build_stages.py > src/splicer/SkSplicer_generated.h
+
+#if defined(__aarch64__)
+
+static const unsigned int kSplice_clear[] = {
+    0x6f00e400,                                 //  movi          v0.2d, #0x0
+    0x6f00e401,                                 //  movi          v1.2d, #0x0
+    0x6f00e402,                                 //  movi          v2.2d, #0x0
+    0x6f00e403,                                 //  movi          v3.2d, #0x0
+};
+static const unsigned int kSplice_plus[] = {
+    0x4e24d400,                                 //  fadd          v0.4s, v0.4s, v4.4s
+    0x4e25d421,                                 //  fadd          v1.4s, v1.4s, v5.4s
+    0x4e26d442,                                 //  fadd          v2.4s, v2.4s, v6.4s
+    0x4e27d463,                                 //  fadd          v3.4s, v3.4s, v7.4s
+};
+static const unsigned int kSplice_srcover[] = {
+    0x91001068,                                 //  add           x8, x3, #0x4
+    0x4d40c910,                                 //  ld1r          {v16.4s}, [x8]
+    0x4ea3d610,                                 //  fsub          v16.4s, v16.4s, v3.4s
+    0x4e24ce00,                                 //  fmla          v0.4s, v16.4s, v4.4s
+    0x4e25ce01,                                 //  fmla          v1.4s, v16.4s, v5.4s
+    0x4e26ce02,                                 //  fmla          v2.4s, v16.4s, v6.4s
+    0x4e26ce03,                                 //  fmla          v3.4s, v16.4s, v6.4s
+};
+static const unsigned int kSplice_dstover[] = {
+    0x91001068,                                 //  add           x8, x3, #0x4
+    0x4d40c910,                                 //  ld1r          {v16.4s}, [x8]
+    0x4ea7d610,                                 //  fsub          v16.4s, v16.4s, v7.4s
+    0x4e20ce04,                                 //  fmla          v4.4s, v16.4s, v0.4s
+    0x4e21ce05,                                 //  fmla          v5.4s, v16.4s, v1.4s
+    0x4e22ce06,                                 //  fmla          v6.4s, v16.4s, v2.4s
+    0x4e22ce07,                                 //  fmla          v7.4s, v16.4s, v2.4s
+};
+static const unsigned int kSplice_clamp_0[] = {
+    0x6f00e410,                                 //  movi          v16.2d, #0x0
+    0x4e30f400,                                 //  fmax          v0.4s, v0.4s, v16.4s
+    0x4e30f421,                                 //  fmax          v1.4s, v1.4s, v16.4s
+    0x4e30f442,                                 //  fmax          v2.4s, v2.4s, v16.4s
+    0x4e30f463,                                 //  fmax          v3.4s, v3.4s, v16.4s
+};
+static const unsigned int kSplice_clamp_1[] = {
+    0x91001068,                                 //  add           x8, x3, #0x4
+    0x4d40c910,                                 //  ld1r          {v16.4s}, [x8]
+    0x4eb0f400,                                 //  fmin          v0.4s, v0.4s, v16.4s
+    0x4eb0f421,                                 //  fmin          v1.4s, v1.4s, v16.4s
+    0x4eb0f442,                                 //  fmin          v2.4s, v2.4s, v16.4s
+    0x4eb0f463,                                 //  fmin          v3.4s, v3.4s, v16.4s
+};
+static const unsigned int kSplice_clamp_a[] = {
+    0x91001068,                                 //  add           x8, x3, #0x4
+    0x4d40c910,                                 //  ld1r          {v16.4s}, [x8]
+    0x4eb0f463,                                 //  fmin          v3.4s, v3.4s, v16.4s
+    0x4ea3f400,                                 //  fmin          v0.4s, v0.4s, v3.4s
+    0x4ea3f421,                                 //  fmin          v1.4s, v1.4s, v3.4s
+    0x4ea3f442,                                 //  fmin          v2.4s, v2.4s, v3.4s
+};
+static const unsigned int kSplice_swap[] = {
+    0x4ea31c70,                                 //  mov           v16.16b, v3.16b
+    0x4ea21c51,                                 //  mov           v17.16b, v2.16b
+    0x4ea11c32,                                 //  mov           v18.16b, v1.16b
+    0x4ea01c13,                                 //  mov           v19.16b, v0.16b
+    0x4ea41c80,                                 //  mov           v0.16b, v4.16b
+    0x4ea51ca1,                                 //  mov           v1.16b, v5.16b
+    0x4ea61cc2,                                 //  mov           v2.16b, v6.16b
+    0x4ea71ce3,                                 //  mov           v3.16b, v7.16b
+    0x4eb31e64,                                 //  mov           v4.16b, v19.16b
+    0x4eb21e45,                                 //  mov           v5.16b, v18.16b
+    0x4eb11e26,                                 //  mov           v6.16b, v17.16b
+    0x4eb01e07,                                 //  mov           v7.16b, v16.16b
+};
+static const unsigned int kSplice_move_src_dst[] = {
+    0x4ea01c04,                                 //  mov           v4.16b, v0.16b
+    0x4ea11c25,                                 //  mov           v5.16b, v1.16b
+    0x4ea21c46,                                 //  mov           v6.16b, v2.16b
+    0x4ea31c67,                                 //  mov           v7.16b, v3.16b
+};
+static const unsigned int kSplice_move_dst_src[] = {
+    0x4ea41c80,                                 //  mov           v0.16b, v4.16b
+    0x4ea51ca1,                                 //  mov           v1.16b, v5.16b
+    0x4ea61cc2,                                 //  mov           v2.16b, v6.16b
+    0x4ea71ce3,                                 //  mov           v3.16b, v7.16b
+};
+static const unsigned int kSplice_premul[] = {
+    0x6e23dc00,                                 //  fmul          v0.4s, v0.4s, v3.4s
+    0x6e23dc21,                                 //  fmul          v1.4s, v1.4s, v3.4s
+    0x6e23dc42,                                 //  fmul          v2.4s, v2.4s, v3.4s
+};
+static const unsigned int kSplice_unpremul[] = {
+    0x91001068,                                 //  add           x8, x3, #0x4
+    0x4d40c910,                                 //  ld1r          {v16.4s}, [x8]
+    0x4ea0d871,                                 //  fcmeq         v17.4s, v3.4s, #0.0
+    0x6e23fe10,                                 //  fdiv          v16.4s, v16.4s, v3.4s
+    0x4e711e10,                                 //  bic           v16.16b, v16.16b, v17.16b
+    0x6e20de00,                                 //  fmul          v0.4s, v16.4s, v0.4s
+    0x6e21de01,                                 //  fmul          v1.4s, v16.4s, v1.4s
+    0x6e22de02,                                 //  fmul          v2.4s, v16.4s, v2.4s
+};
+static const unsigned int kSplice_from_srgb[] = {
+    0x91005068,                                 //  add           x8, x3, #0x14
+    0x4d40c910,                                 //  ld1r          {v16.4s}, [x8]
+    0x91004068,                                 //  add           x8, x3, #0x10
+    0x4d40c911,                                 //  ld1r          {v17.4s}, [x8]
+    0x2d434c72,                                 //  ldp           s18, s19, [x3,#24]
+    0x6e22dc54,                                 //  fmul          v20.4s, v2.4s, v2.4s
+    0x4eb01e15,                                 //  mov           v21.16b, v16.16b
+    0x4eb01e17,                                 //  mov           v23.16b, v16.16b
+    0x4f921050,                                 //  fmla          v16.4s, v2.4s, v18.s[0]
+    0x4eb11e36,                                 //  mov           v22.16b, v17.16b
+    0x4eb11e38,                                 //  mov           v24.16b, v17.16b
+    0x4e34ce11,                                 //  fmla          v17.4s, v16.4s, v20.4s
+    0x6e20dc10,                                 //  fmul          v16.4s, v0.4s, v0.4s
+    0x91008068,                                 //  add           x8, x3, #0x20
+    0x4f921015,                                 //  fmla          v21.4s, v0.4s, v18.s[0]
+    0x4e30ceb6,                                 //  fmla          v22.4s, v21.4s, v16.4s
+    0x4d40c910,                                 //  ld1r          {v16.4s}, [x8]
+    0x6e21dc34,                                 //  fmul          v20.4s, v1.4s, v1.4s
+    0x4f921037,                                 //  fmla          v23.4s, v1.4s, v18.s[0]
+    0x4f939015,                                 //  fmul          v21.4s, v0.4s, v19.s[0]
+    0x4f939032,                                 //  fmul          v18.4s, v1.4s, v19.s[0]
+    0x4f939053,                                 //  fmul          v19.4s, v2.4s, v19.s[0]
+    0x6ea0e600,                                 //  fcmgt         v0.4s, v16.4s, v0.4s
+    0x6ea1e601,                                 //  fcmgt         v1.4s, v16.4s, v1.4s
+    0x6ea2e602,                                 //  fcmgt         v2.4s, v16.4s, v2.4s
+    0x4e34cef8,                                 //  fmla          v24.4s, v23.4s, v20.4s
+    0x6e761ea0,                                 //  bsl           v0.16b, v21.16b, v22.16b
+    0x6e781e41,                                 //  bsl           v1.16b, v18.16b, v24.16b
+    0x6e711e62,                                 //  bsl           v2.16b, v19.16b, v17.16b
+};
+static const unsigned int kSplice_to_srgb[] = {
+    0x6ea1d810,                                 //  frsqrte       v16.4s, v0.4s
+    0x6ea1d835,                                 //  frsqrte       v21.4s, v1.4s
+    0x6e30de17,                                 //  fmul          v23.4s, v16.4s, v16.4s
+    0x6ea1d856,                                 //  frsqrte       v22.4s, v2.4s
+    0x6e35deb9,                                 //  fmul          v25.4s, v21.4s, v21.4s
+    0x4eb7fc17,                                 //  frsqrts       v23.4s, v0.4s, v23.4s
+    0x9100c068,                                 //  add           x8, x3, #0x30
+    0x6e36deda,                                 //  fmul          v26.4s, v22.4s, v22.4s
+    0x4eb9fc39,                                 //  frsqrts       v25.4s, v1.4s, v25.4s
+    0x6e37de10,                                 //  fmul          v16.4s, v16.4s, v23.4s
+    0x2d44c871,                                 //  ldp           s17, s18, [x3,#36]
+    0x4d40c914,                                 //  ld1r          {v20.4s}, [x8]
+    0x4ebafc5a,                                 //  frsqrts       v26.4s, v2.4s, v26.4s
+    0x6e39deb5,                                 //  fmul          v21.4s, v21.4s, v25.4s
+    0x4ea1da17,                                 //  frecpe        v23.4s, v16.4s
+    0xbd402c73,                                 //  ldr           s19, [x3,#44]
+    0x9100d068,                                 //  add           x8, x3, #0x34
+    0x6e3aded6,                                 //  fmul          v22.4s, v22.4s, v26.4s
+    0x4ea1dabb,                                 //  frecpe        v27.4s, v21.4s
+    0x4e37fe1d,                                 //  frecps        v29.4s, v16.4s, v23.4s
+    0x4d40c918,                                 //  ld1r          {v24.4s}, [x8]
+    0x4ea1dadc,                                 //  frecpe        v28.4s, v22.4s
+    0x6e3ddef7,                                 //  fmul          v23.4s, v23.4s, v29.4s
+    0x4e3bfebd,                                 //  frecps        v29.4s, v21.4s, v27.4s
+    0x6e3ddf7b,                                 //  fmul          v27.4s, v27.4s, v29.4s
+    0x4e3cfedd,                                 //  frecps        v29.4s, v22.4s, v28.4s
+    0x6e3ddf9c,                                 //  fmul          v28.4s, v28.4s, v29.4s
+    0x4eb41e9d,                                 //  mov           v29.16b, v20.16b
+    0x6ea1da19,                                 //  frsqrte       v25.4s, v16.4s
+    0x4f9312fd,                                 //  fmla          v29.4s, v23.4s, v19.s[0]
+    0x4eb41e97,                                 //  mov           v23.16b, v20.16b
+    0x4f91901a,                                 //  fmul          v26.4s, v0.4s, v17.s[0]
+    0x4f931377,                                 //  fmla          v23.4s, v27.4s, v19.s[0]
+    0x6ea1dabb,                                 //  frsqrte       v27.4s, v21.4s
+    0x4f931394,                                 //  fmla          v20.4s, v28.4s, v19.s[0]
+    0x4f919033,                                 //  fmul          v19.4s, v1.4s, v17.s[0]
+    0x4f919051,                                 //  fmul          v17.4s, v2.4s, v17.s[0]
+    0x6ea0e700,                                 //  fcmgt         v0.4s, v24.4s, v0.4s
+    0x6ea1e701,                                 //  fcmgt         v1.4s, v24.4s, v1.4s
+    0x6ea2e702,                                 //  fcmgt         v2.4s, v24.4s, v2.4s
+    0x6e39df38,                                 //  fmul          v24.4s, v25.4s, v25.4s
+    0x6ea1dadc,                                 //  frsqrte       v28.4s, v22.4s
+    0x4eb8fe10,                                 //  frsqrts       v16.4s, v16.4s, v24.4s
+    0x6e3bdf78,                                 //  fmul          v24.4s, v27.4s, v27.4s
+    0x4eb8feb5,                                 //  frsqrts       v21.4s, v21.4s, v24.4s
+    0x6e3cdf98,                                 //  fmul          v24.4s, v28.4s, v28.4s
+    0x91001068,                                 //  add           x8, x3, #0x4
+    0x4eb8fed6,                                 //  frsqrts       v22.4s, v22.4s, v24.4s
+    0x4d40c918,                                 //  ld1r          {v24.4s}, [x8]
+    0x6e30df30,                                 //  fmul          v16.4s, v25.4s, v16.4s
+    0x6e35df75,                                 //  fmul          v21.4s, v27.4s, v21.4s
+    0x6e36df96,                                 //  fmul          v22.4s, v28.4s, v22.4s
+    0x4f92121d,                                 //  fmla          v29.4s, v16.4s, v18.s[0]
+    0x4f9212b7,                                 //  fmla          v23.4s, v21.4s, v18.s[0]
+    0x4f9212d4,                                 //  fmla          v20.4s, v22.4s, v18.s[0]
+    0x4ebdf710,                                 //  fmin          v16.4s, v24.4s, v29.4s
+    0x4eb7f712,                                 //  fmin          v18.4s, v24.4s, v23.4s
+    0x4eb4f714,                                 //  fmin          v20.4s, v24.4s, v20.4s
+    0x6e701f40,                                 //  bsl           v0.16b, v26.16b, v16.16b
+    0x6e721e61,                                 //  bsl           v1.16b, v19.16b, v18.16b
+    0x6e741e22,                                 //  bsl           v2.16b, v17.16b, v20.16b
+};
+static const unsigned int kSplice_scale_u8[] = {
+    0xf9400048,                                 //  ldr           x8, [x2]
+    0xbd400c71,                                 //  ldr           s17, [x3,#12]
+    0x8b000108,                                 //  add           x8, x8, x0
+    0x39400109,                                 //  ldrb          w9, [x8]
+    0x3940050a,                                 //  ldrb          w10, [x8,#1]
+    0x4e021d30,                                 //  mov           v16.h[0], w9
+    0x39400909,                                 //  ldrb          w9, [x8,#2]
+    0x39400d08,                                 //  ldrb          w8, [x8,#3]
+    0x4e061d50,                                 //  mov           v16.h[1], w10
+    0x4e0a1d30,                                 //  mov           v16.h[2], w9
+    0x4e0e1d10,                                 //  mov           v16.h[3], w8
+    0x2f07b7f0,                                 //  bic           v16.4h, #0xff, lsl #8
+    0x2f10a610,                                 //  uxtl          v16.4s, v16.4h
+    0x6e21da10,                                 //  ucvtf         v16.4s, v16.4s
+    0x4f919210,                                 //  fmul          v16.4s, v16.4s, v17.s[0]
+    0x6e20de00,                                 //  fmul          v0.4s, v16.4s, v0.4s
+    0x6e21de01,                                 //  fmul          v1.4s, v16.4s, v1.4s
+    0x6e22de02,                                 //  fmul          v2.4s, v16.4s, v2.4s
+    0x6e23de03,                                 //  fmul          v3.4s, v16.4s, v3.4s
+};
+static const unsigned int kSplice_load_8888[] = {
+    0xf9400048,                                 //  ldr           x8, [x2]
+    0xd37ef409,                                 //  lsl           x9, x0, #2
+    0x4d40c860,                                 //  ld1r          {v0.4s}, [x3]
+    0xbd400c63,                                 //  ldr           s3, [x3,#12]
+    0x3ce96901,                                 //  ldr           q1, [x8,x9]
+    0x4e211c02,                                 //  and           v2.16b, v0.16b, v1.16b
+    0x6f380430,                                 //  ushr          v16.4s, v1.4s, #8
+    0x6f300431,                                 //  ushr          v17.4s, v1.4s, #16
+    0x6f280421,                                 //  ushr          v1.4s, v1.4s, #24
+    0x4e21d842,                                 //  scvtf         v2.4s, v2.4s
+    0x4e301c10,                                 //  and           v16.16b, v0.16b, v16.16b
+    0x4e311c11,                                 //  and           v17.16b, v0.16b, v17.16b
+    0x4e21d832,                                 //  scvtf         v18.4s, v1.4s
+    0x4f839040,                                 //  fmul          v0.4s, v2.4s, v3.s[0]
+    0x4e21da01,                                 //  scvtf         v1.4s, v16.4s
+    0x4e21da22,                                 //  scvtf         v2.4s, v17.4s
+    0x4f839021,                                 //  fmul          v1.4s, v1.4s, v3.s[0]
+    0x4f839042,                                 //  fmul          v2.4s, v2.4s, v3.s[0]
+    0x4f839243,                                 //  fmul          v3.4s, v18.4s, v3.s[0]
+};
+static const unsigned int kSplice_store_8888[] = {
+    0xbd400870,                                 //  ldr           s16, [x3,#8]
+    0xf9400048,                                 //  ldr           x8, [x2]
+    0xd37ef409,                                 //  lsl           x9, x0, #2
+    0x4f909032,                                 //  fmul          v18.4s, v1.4s, v16.s[0]
+    0x4f909011,                                 //  fmul          v17.4s, v0.4s, v16.s[0]
+    0x6e21aa52,                                 //  fcvtnu        v18.4s, v18.4s
+    0x6e21aa31,                                 //  fcvtnu        v17.4s, v17.4s
+    0x4f285652,                                 //  shl           v18.4s, v18.4s, #8
+    0x4eb11e51,                                 //  orr           v17.16b, v18.16b, v17.16b
+    0x4f909052,                                 //  fmul          v18.4s, v2.4s, v16.s[0]
+    0x4f909070,                                 //  fmul          v16.4s, v3.4s, v16.s[0]
+    0x6e21aa52,                                 //  fcvtnu        v18.4s, v18.4s
+    0x6e21aa10,                                 //  fcvtnu        v16.4s, v16.4s
+    0x4f305652,                                 //  shl           v18.4s, v18.4s, #16
+    0x4eb21e31,                                 //  orr           v17.16b, v17.16b, v18.16b
+    0x4f385610,                                 //  shl           v16.4s, v16.4s, #24
+    0x4eb01e30,                                 //  orr           v16.16b, v17.16b, v16.16b
+    0x3ca96910,                                 //  str           q16, [x8,x9]
+};
+static const unsigned int kSplice_load_f16[] = {
+    0xf9400048,                                 //  ldr           x8, [x2]
+    0x8b000d08,                                 //  add           x8, x8, x0, lsl #3
+    0x0c400510,                                 //  ld4           {v16.4h-v19.4h}, [x8]
+    0x0e217a00,                                 //  fcvtl         v0.4s, v16.4h
+    0x0e217a21,                                 //  fcvtl         v1.4s, v17.4h
+    0x0e217a42,                                 //  fcvtl         v2.4s, v18.4h
+    0x0e217a63,                                 //  fcvtl         v3.4s, v19.4h
+};
+static const unsigned int kSplice_store_f16[] = {
+    0xf9400048,                                 //  ldr           x8, [x2]
+    0x0e216810,                                 //  fcvtn         v16.4h, v0.4s
+    0x0e216831,                                 //  fcvtn         v17.4h, v1.4s
+    0x0e216852,                                 //  fcvtn         v18.4h, v2.4s
+    0x8b000d08,                                 //  add           x8, x8, x0, lsl #3
+    0x0e216873,                                 //  fcvtn         v19.4h, v3.4s
+    0x0c000510,                                 //  st4           {v16.4h-v19.4h}, [x8]
+};
+
+#else
+
+static const unsigned char kSplice_clear[] = {
+    0xc5,0xfc,0x57,0xc0,                        //  vxorps        %ymm0,%ymm0,%ymm0
+    0xc5,0xf4,0x57,0xc9,                        //  vxorps        %ymm1,%ymm1,%ymm1
+    0xc5,0xec,0x57,0xd2,                        //  vxorps        %ymm2,%ymm2,%ymm2
+    0xc5,0xe4,0x57,0xdb,                        //  vxorps        %ymm3,%ymm3,%ymm3
+};
+static const unsigned char kSplice_plus[] = {
+    0xc5,0xfc,0x58,0xc4,                        //  vaddps        %ymm4,%ymm0,%ymm0
+    0xc5,0xf4,0x58,0xcd,                        //  vaddps        %ymm5,%ymm1,%ymm1
+    0xc5,0xec,0x58,0xd6,                        //  vaddps        %ymm6,%ymm2,%ymm2
+    0xc5,0xe4,0x58,0xdf,                        //  vaddps        %ymm7,%ymm3,%ymm3
+};
+static const unsigned char kSplice_srcover[] = {
+    0xc4,0x62,0x7d,0x18,0x41,0x04,              //  vbroadcastss  0x4(%rcx),%ymm8
+    0xc5,0x3c,0x5c,0xc3,                        //  vsubps        %ymm3,%ymm8,%ymm8
+    0xc4,0xc2,0x5d,0xb8,0xc0,                   //  vfmadd231ps   %ymm8,%ymm4,%ymm0
+    0xc4,0xc2,0x55,0xb8,0xc8,                   //  vfmadd231ps   %ymm8,%ymm5,%ymm1
+    0xc4,0xc2,0x4d,0xb8,0xd0,                   //  vfmadd231ps   %ymm8,%ymm6,%ymm2
+    0xc4,0xc2,0x4d,0xb8,0xd8,                   //  vfmadd231ps   %ymm8,%ymm6,%ymm3
+};
+static const unsigned char kSplice_dstover[] = {
+    0xc4,0x62,0x7d,0x18,0x41,0x04,              //  vbroadcastss  0x4(%rcx),%ymm8
+    0xc5,0x3c,0x5c,0xc7,                        //  vsubps        %ymm7,%ymm8,%ymm8
+    0xc4,0xc2,0x7d,0xb8,0xe0,                   //  vfmadd231ps   %ymm8,%ymm0,%ymm4
+    0xc4,0xc2,0x75,0xb8,0xe8,                   //  vfmadd231ps   %ymm8,%ymm1,%ymm5
+    0xc4,0xc2,0x6d,0xb8,0xf0,                   //  vfmadd231ps   %ymm8,%ymm2,%ymm6
+    0xc4,0xc2,0x6d,0xb8,0xf8,                   //  vfmadd231ps   %ymm8,%ymm2,%ymm7
+};
+static const unsigned char kSplice_clamp_0[] = {
+    0xc4,0x41,0x3c,0x57,0xc0,                   //  vxorps        %ymm8,%ymm8,%ymm8
+    0xc4,0xc1,0x7c,0x5f,0xc0,                   //  vmaxps        %ymm8,%ymm0,%ymm0
+    0xc4,0xc1,0x74,0x5f,0xc8,                   //  vmaxps        %ymm8,%ymm1,%ymm1
+    0xc4,0xc1,0x6c,0x5f,0xd0,                   //  vmaxps        %ymm8,%ymm2,%ymm2
+    0xc4,0xc1,0x64,0x5f,0xd8,                   //  vmaxps        %ymm8,%ymm3,%ymm3
+};
+static const unsigned char kSplice_clamp_1[] = {
+    0xc4,0x62,0x7d,0x18,0x41,0x04,              //  vbroadcastss  0x4(%rcx),%ymm8
+    0xc4,0xc1,0x7c,0x5d,0xc0,                   //  vminps        %ymm8,%ymm0,%ymm0
+    0xc4,0xc1,0x74,0x5d,0xc8,                   //  vminps        %ymm8,%ymm1,%ymm1
+    0xc4,0xc1,0x6c,0x5d,0xd0,                   //  vminps        %ymm8,%ymm2,%ymm2
+    0xc4,0xc1,0x64,0x5d,0xd8,                   //  vminps        %ymm8,%ymm3,%ymm3
+};
+static const unsigned char kSplice_clamp_a[] = {
+    0xc4,0x62,0x7d,0x18,0x41,0x04,              //  vbroadcastss  0x4(%rcx),%ymm8
+    0xc4,0xc1,0x64,0x5d,0xd8,                   //  vminps        %ymm8,%ymm3,%ymm3
+    0xc5,0xfc,0x5d,0xc3,                        //  vminps        %ymm3,%ymm0,%ymm0
+    0xc5,0xf4,0x5d,0xcb,                        //  vminps        %ymm3,%ymm1,%ymm1
+    0xc5,0xec,0x5d,0xd3,                        //  vminps        %ymm3,%ymm2,%ymm2
+};
+static const unsigned char kSplice_swap[] = {
+    0xc5,0x7c,0x28,0xc3,                        //  vmovaps       %ymm3,%ymm8
+    0xc5,0x7c,0x28,0xca,                        //  vmovaps       %ymm2,%ymm9
+    0xc5,0x7c,0x28,0xd1,                        //  vmovaps       %ymm1,%ymm10
+    0xc5,0x7c,0x28,0xd8,                        //  vmovaps       %ymm0,%ymm11
+    0xc5,0xfc,0x28,0xc4,                        //  vmovaps       %ymm4,%ymm0
+    0xc5,0xfc,0x28,0xcd,                        //  vmovaps       %ymm5,%ymm1
+    0xc5,0xfc,0x28,0xd6,                        //  vmovaps       %ymm6,%ymm2
+    0xc5,0xfc,0x28,0xdf,                        //  vmovaps       %ymm7,%ymm3
+    0xc5,0x7c,0x29,0xdc,                        //  vmovaps       %ymm11,%ymm4
+    0xc5,0x7c,0x29,0xd5,                        //  vmovaps       %ymm10,%ymm5
+    0xc5,0x7c,0x29,0xce,                        //  vmovaps       %ymm9,%ymm6
+    0xc5,0x7c,0x29,0xc7,                        //  vmovaps       %ymm8,%ymm7
+};
+static const unsigned char kSplice_move_src_dst[] = {
+    0xc5,0xfc,0x28,0xe0,                        //  vmovaps       %ymm0,%ymm4
+    0xc5,0xfc,0x28,0xe9,                        //  vmovaps       %ymm1,%ymm5
+    0xc5,0xfc,0x28,0xf2,                        //  vmovaps       %ymm2,%ymm6
+    0xc5,0xfc,0x28,0xfb,                        //  vmovaps       %ymm3,%ymm7
+};
+static const unsigned char kSplice_move_dst_src[] = {
+    0xc5,0xfc,0x28,0xc4,                        //  vmovaps       %ymm4,%ymm0
+    0xc5,0xfc,0x28,0xcd,                        //  vmovaps       %ymm5,%ymm1
+    0xc5,0xfc,0x28,0xd6,                        //  vmovaps       %ymm6,%ymm2
+    0xc5,0xfc,0x28,0xdf,                        //  vmovaps       %ymm7,%ymm3
+};
+static const unsigned char kSplice_premul[] = {
+    0xc5,0xfc,0x59,0xc3,                        //  vmulps        %ymm3,%ymm0,%ymm0
+    0xc5,0xf4,0x59,0xcb,                        //  vmulps        %ymm3,%ymm1,%ymm1
+    0xc5,0xec,0x59,0xd3,                        //  vmulps        %ymm3,%ymm2,%ymm2
+};
+static const unsigned char kSplice_unpremul[] = {
+    0xc4,0x41,0x3c,0x57,0xc0,                   //  vxorps        %ymm8,%ymm8,%ymm8
+    0xc4,0x41,0x64,0xc2,0xc8,0x00,              //  vcmpeqps      %ymm8,%ymm3,%ymm9
+    0xc4,0x62,0x7d,0x18,0x51,0x04,              //  vbroadcastss  0x4(%rcx),%ymm10
+    0xc5,0x2c,0x5e,0xd3,                        //  vdivps        %ymm3,%ymm10,%ymm10
+    0xc4,0x43,0x2d,0x4a,0xc0,0x90,              //  vblendvps     %ymm9,%ymm8,%ymm10,%ymm8
+    0xc5,0xbc,0x59,0xc0,                        //  vmulps        %ymm0,%ymm8,%ymm0
+    0xc5,0xbc,0x59,0xc9,                        //  vmulps        %ymm1,%ymm8,%ymm1
+    0xc5,0xbc,0x59,0xd2,                        //  vmulps        %ymm2,%ymm8,%ymm2
+};
+static const unsigned char kSplice_from_srgb[] = {
+    0xc4,0x62,0x7d,0x18,0x41,0x1c,              //  vbroadcastss  0x1c(%rcx),%ymm8
+    0xc5,0x3c,0x59,0xc8,                        //  vmulps        %ymm0,%ymm8,%ymm9
+    0xc5,0x7c,0x59,0xd0,                        //  vmulps        %ymm0,%ymm0,%ymm10
+    0xc4,0x62,0x7d,0x18,0x59,0x18,              //  vbroadcastss  0x18(%rcx),%ymm11
+    0xc4,0x62,0x7d,0x18,0x61,0x14,              //  vbroadcastss  0x14(%rcx),%ymm12
+    0xc4,0x41,0x7c,0x28,0xeb,                   //  vmovaps       %ymm11,%ymm13
+    0xc4,0x42,0x7d,0xa8,0xec,                   //  vfmadd213ps   %ymm12,%ymm0,%ymm13
+    0xc4,0x62,0x7d,0x18,0x71,0x10,              //  vbroadcastss  0x10(%rcx),%ymm14
+    0xc4,0x42,0x2d,0xa8,0xee,                   //  vfmadd213ps   %ymm14,%ymm10,%ymm13
+    0xc4,0x62,0x7d,0x18,0x51,0x20,              //  vbroadcastss  0x20(%rcx),%ymm10
+    0xc4,0xc1,0x7c,0xc2,0xc2,0x01,              //  vcmpltps      %ymm10,%ymm0,%ymm0
+    0xc4,0xc3,0x15,0x4a,0xc1,0x00,              //  vblendvps     %ymm0,%ymm9,%ymm13,%ymm0
+    0xc5,0x3c,0x59,0xc9,                        //  vmulps        %ymm1,%ymm8,%ymm9
+    0xc5,0x74,0x59,0xe9,                        //  vmulps        %ymm1,%ymm1,%ymm13
+    0xc4,0x41,0x7c,0x28,0xfb,                   //  vmovaps       %ymm11,%ymm15
+    0xc4,0x42,0x75,0xa8,0xfc,                   //  vfmadd213ps   %ymm12,%ymm1,%ymm15
+    0xc4,0x42,0x15,0xa8,0xfe,                   //  vfmadd213ps   %ymm14,%ymm13,%ymm15
+    0xc4,0xc1,0x74,0xc2,0xca,0x01,              //  vcmpltps      %ymm10,%ymm1,%ymm1
+    0xc4,0xc3,0x05,0x4a,0xc9,0x10,              //  vblendvps     %ymm1,%ymm9,%ymm15,%ymm1
+    0xc5,0x3c,0x59,0xc2,                        //  vmulps        %ymm2,%ymm8,%ymm8
+    0xc5,0x6c,0x59,0xca,                        //  vmulps        %ymm2,%ymm2,%ymm9
+    0xc4,0x42,0x6d,0xa8,0xdc,                   //  vfmadd213ps   %ymm12,%ymm2,%ymm11
+    0xc4,0x42,0x35,0xa8,0xde,                   //  vfmadd213ps   %ymm14,%ymm9,%ymm11
+    0xc4,0xc1,0x6c,0xc2,0xd2,0x01,              //  vcmpltps      %ymm10,%ymm2,%ymm2
+    0xc4,0xc3,0x25,0x4a,0xd0,0x20,              //  vblendvps     %ymm2,%ymm8,%ymm11,%ymm2
+};
+static const unsigned char kSplice_to_srgb[] = {
+    0xc5,0x7c,0x52,0xc0,                        //  vrsqrtps      %ymm0,%ymm8
+    0xc4,0x41,0x7c,0x53,0xc8,                   //  vrcpps        %ymm8,%ymm9
+    0xc4,0x41,0x7c,0x52,0xd0,                   //  vrsqrtps      %ymm8,%ymm10
+    0xc4,0x62,0x7d,0x18,0x41,0x24,              //  vbroadcastss  0x24(%rcx),%ymm8
+    0xc5,0x3c,0x59,0xd8,                        //  vmulps        %ymm0,%ymm8,%ymm11
+    0xc4,0x62,0x7d,0x18,0x61,0x04,              //  vbroadcastss  0x4(%rcx),%ymm12
+    0xc4,0x62,0x7d,0x18,0x69,0x28,              //  vbroadcastss  0x28(%rcx),%ymm13
+    0xc4,0x62,0x7d,0x18,0x71,0x2c,              //  vbroadcastss  0x2c(%rcx),%ymm14
+    0xc4,0x62,0x7d,0x18,0x79,0x30,              //  vbroadcastss  0x30(%rcx),%ymm15
+    0xc4,0x42,0x0d,0xa8,0xcf,                   //  vfmadd213ps   %ymm15,%ymm14,%ymm9
+    0xc4,0x42,0x15,0xb8,0xca,                   //  vfmadd231ps   %ymm10,%ymm13,%ymm9
+    0xc4,0x41,0x1c,0x5d,0xc9,                   //  vminps        %ymm9,%ymm12,%ymm9
+    0xc4,0x62,0x7d,0x18,0x51,0x34,              //  vbroadcastss  0x34(%rcx),%ymm10
+    0xc4,0xc1,0x7c,0xc2,0xc2,0x01,              //  vcmpltps      %ymm10,%ymm0,%ymm0
+    0xc4,0xc3,0x35,0x4a,0xc3,0x00,              //  vblendvps     %ymm0,%ymm11,%ymm9,%ymm0
+    0xc5,0x7c,0x52,0xc9,                        //  vrsqrtps      %ymm1,%ymm9
+    0xc4,0x41,0x7c,0x53,0xd9,                   //  vrcpps        %ymm9,%ymm11
+    0xc4,0x41,0x7c,0x52,0xc9,                   //  vrsqrtps      %ymm9,%ymm9
+    0xc4,0x42,0x0d,0xa8,0xdf,                   //  vfmadd213ps   %ymm15,%ymm14,%ymm11
+    0xc4,0x42,0x15,0xb8,0xd9,                   //  vfmadd231ps   %ymm9,%ymm13,%ymm11
+    0xc5,0x3c,0x59,0xc9,                        //  vmulps        %ymm1,%ymm8,%ymm9
+    0xc4,0x41,0x1c,0x5d,0xdb,                   //  vminps        %ymm11,%ymm12,%ymm11
+    0xc4,0xc1,0x74,0xc2,0xca,0x01,              //  vcmpltps      %ymm10,%ymm1,%ymm1
+    0xc4,0xc3,0x25,0x4a,0xc9,0x10,              //  vblendvps     %ymm1,%ymm9,%ymm11,%ymm1
+    0xc5,0x7c,0x52,0xca,                        //  vrsqrtps      %ymm2,%ymm9
+    0xc4,0x41,0x7c,0x53,0xd9,                   //  vrcpps        %ymm9,%ymm11
+    0xc4,0x42,0x0d,0xa8,0xdf,                   //  vfmadd213ps   %ymm15,%ymm14,%ymm11
+    0xc4,0x41,0x7c,0x52,0xc9,                   //  vrsqrtps      %ymm9,%ymm9
+    0xc4,0x42,0x15,0xb8,0xd9,                   //  vfmadd231ps   %ymm9,%ymm13,%ymm11
+    0xc4,0x41,0x1c,0x5d,0xcb,                   //  vminps        %ymm11,%ymm12,%ymm9
+    0xc5,0x3c,0x59,0xc2,                        //  vmulps        %ymm2,%ymm8,%ymm8
+    0xc4,0xc1,0x6c,0xc2,0xd2,0x01,              //  vcmpltps      %ymm10,%ymm2,%ymm2
+    0xc4,0xc3,0x35,0x4a,0xd0,0x20,              //  vblendvps     %ymm2,%ymm8,%ymm9,%ymm2
+};
+static const unsigned char kSplice_scale_u8[] = {
+    0x48,0x8b,0x02,                             //  mov           (%rdx),%rax
+    0xc4,0x62,0x7d,0x31,0x04,0x38,              //  vpmovzxbd     (%rax,%rdi,1),%ymm8
+    0xc4,0x41,0x7c,0x5b,0xc0,                   //  vcvtdq2ps     %ymm8,%ymm8
+    0xc4,0x62,0x7d,0x18,0x49,0x0c,              //  vbroadcastss  0xc(%rcx),%ymm9
+    0xc4,0x41,0x3c,0x59,0xc1,                   //  vmulps        %ymm9,%ymm8,%ymm8
+    0xc5,0xbc,0x59,0xc0,                        //  vmulps        %ymm0,%ymm8,%ymm0
+    0xc5,0xbc,0x59,0xc9,                        //  vmulps        %ymm1,%ymm8,%ymm1
+    0xc5,0xbc,0x59,0xd2,                        //  vmulps        %ymm2,%ymm8,%ymm2
+    0xc5,0xbc,0x59,0xdb,                        //  vmulps        %ymm3,%ymm8,%ymm3
+};
+static const unsigned char kSplice_load_8888[] = {
+    0x48,0x8b,0x02,                             //  mov           (%rdx),%rax
+    0xc5,0xfc,0x10,0x1c,0xb8,                   //  vmovups       (%rax,%rdi,4),%ymm3
+    0xc4,0xe2,0x7d,0x18,0x11,                   //  vbroadcastss  (%rcx),%ymm2
+    0xc5,0xec,0x54,0xc3,                        //  vandps        %ymm3,%ymm2,%ymm0
+    0xc5,0xfc,0x5b,0xc0,                        //  vcvtdq2ps     %ymm0,%ymm0
+    0xc4,0x62,0x7d,0x18,0x41,0x0c,              //  vbroadcastss  0xc(%rcx),%ymm8
+    0xc5,0xbc,0x59,0xc0,                        //  vmulps        %ymm0,%ymm8,%ymm0
+    0xc5,0xf5,0x72,0xd3,0x08,                   //  vpsrld        $0x8,%ymm3,%ymm1
+    0xc5,0xec,0x54,0xc9,                        //  vandps        %ymm1,%ymm2,%ymm1
+    0xc5,0xfc,0x5b,0xc9,                        //  vcvtdq2ps     %ymm1,%ymm1
+    0xc5,0xbc,0x59,0xc9,                        //  vmulps        %ymm1,%ymm8,%ymm1
+    0xc5,0xb5,0x72,0xd3,0x10,                   //  vpsrld        $0x10,%ymm3,%ymm9
+    0xc4,0xc1,0x6c,0x54,0xd1,                   //  vandps        %ymm9,%ymm2,%ymm2
+    0xc5,0xfc,0x5b,0xd2,                        //  vcvtdq2ps     %ymm2,%ymm2
+    0xc5,0xbc,0x59,0xd2,                        //  vmulps        %ymm2,%ymm8,%ymm2
+    0xc5,0xe5,0x72,0xd3,0x18,                   //  vpsrld        $0x18,%ymm3,%ymm3
+    0xc5,0xfc,0x5b,0xdb,                        //  vcvtdq2ps     %ymm3,%ymm3
+    0xc4,0xc1,0x64,0x59,0xd8,                   //  vmulps        %ymm8,%ymm3,%ymm3
+};
+static const unsigned char kSplice_store_8888[] = {
+    0x48,0x8b,0x02,                             //  mov           (%rdx),%rax
+    0xc4,0x62,0x7d,0x18,0x41,0x08,              //  vbroadcastss  0x8(%rcx),%ymm8
+    0xc5,0x3c,0x59,0xc8,                        //  vmulps        %ymm0,%ymm8,%ymm9
+    0xc4,0x41,0x7d,0x5b,0xc9,                   //  vcvtps2dq     %ymm9,%ymm9
+    0xc5,0x3c,0x59,0xd1,                        //  vmulps        %ymm1,%ymm8,%ymm10
+    0xc4,0x41,0x7d,0x5b,0xd2,                   //  vcvtps2dq     %ymm10,%ymm10
+    0xc4,0xc1,0x2d,0x72,0xf2,0x08,              //  vpslld        $0x8,%ymm10,%ymm10
+    0xc4,0x41,0x2d,0xeb,0xc9,                   //  vpor          %ymm9,%ymm10,%ymm9
+    0xc5,0x3c,0x59,0xd2,                        //  vmulps        %ymm2,%ymm8,%ymm10
+    0xc4,0x41,0x7d,0x5b,0xd2,                   //  vcvtps2dq     %ymm10,%ymm10
+    0xc4,0xc1,0x2d,0x72,0xf2,0x10,              //  vpslld        $0x10,%ymm10,%ymm10
+    0xc5,0x3c,0x59,0xc3,                        //  vmulps        %ymm3,%ymm8,%ymm8
+    0xc4,0x41,0x7d,0x5b,0xc0,                   //  vcvtps2dq     %ymm8,%ymm8
+    0xc4,0xc1,0x3d,0x72,0xf0,0x18,              //  vpslld        $0x18,%ymm8,%ymm8
+    0xc4,0x41,0x2d,0xeb,0xc0,                   //  vpor          %ymm8,%ymm10,%ymm8
+    0xc4,0x41,0x35,0xeb,0xc0,                   //  vpor          %ymm8,%ymm9,%ymm8
+    0xc5,0x7e,0x7f,0x04,0xb8,                   //  vmovdqu       %ymm8,(%rax,%rdi,4)
+};
+static const unsigned char kSplice_load_f16[] = {
+    0x48,0x8b,0x02,                             //  mov           (%rdx),%rax
+    0xc5,0xfa,0x6f,0x04,0xf8,                   //  vmovdqu       (%rax,%rdi,8),%xmm0
+    0xc5,0xfa,0x6f,0x4c,0xf8,0x10,              //  vmovdqu       0x10(%rax,%rdi,8),%xmm1
+    0xc5,0xfa,0x6f,0x54,0xf8,0x20,              //  vmovdqu       0x20(%rax,%rdi,8),%xmm2
+    0xc5,0xfa,0x6f,0x5c,0xf8,0x30,              //  vmovdqu       0x30(%rax,%rdi,8),%xmm3
+    0xc5,0x79,0x61,0xc1,                        //  vpunpcklwd    %xmm1,%xmm0,%xmm8
+    0xc5,0xf9,0x69,0xc1,                        //  vpunpckhwd    %xmm1,%xmm0,%xmm0
+    0xc5,0xe9,0x61,0xcb,                        //  vpunpcklwd    %xmm3,%xmm2,%xmm1
+    0xc5,0xe9,0x69,0xd3,                        //  vpunpckhwd    %xmm3,%xmm2,%xmm2
+    0xc5,0x39,0x61,0xc8,                        //  vpunpcklwd    %xmm0,%xmm8,%xmm9
+    0xc5,0x39,0x69,0xc0,                        //  vpunpckhwd    %xmm0,%xmm8,%xmm8
+    0xc5,0xf1,0x61,0xda,                        //  vpunpcklwd    %xmm2,%xmm1,%xmm3
+    0xc5,0x71,0x69,0xd2,                        //  vpunpckhwd    %xmm2,%xmm1,%xmm10
+    0xc5,0xb1,0x6c,0xc3,                        //  vpunpcklqdq   %xmm3,%xmm9,%xmm0
+    0xc4,0xe2,0x7d,0x13,0xc0,                   //  vcvtph2ps     %xmm0,%ymm0
+    0xc5,0xb1,0x6d,0xcb,                        //  vpunpckhqdq   %xmm3,%xmm9,%xmm1
+    0xc4,0xe2,0x7d,0x13,0xc9,                   //  vcvtph2ps     %xmm1,%ymm1
+    0xc4,0xc1,0x39,0x6c,0xd2,                   //  vpunpcklqdq   %xmm10,%xmm8,%xmm2
+    0xc4,0xe2,0x7d,0x13,0xd2,                   //  vcvtph2ps     %xmm2,%ymm2
+    0xc4,0xc1,0x39,0x6d,0xda,                   //  vpunpckhqdq   %xmm10,%xmm8,%xmm3
+    0xc4,0xe2,0x7d,0x13,0xdb,                   //  vcvtph2ps     %xmm3,%ymm3
+};
+static const unsigned char kSplice_store_f16[] = {
+    0x48,0x8b,0x02,                             //  mov           (%rdx),%rax
+    0xc4,0xc3,0x7d,0x1d,0xc0,0x04,              //  vcvtps2ph     $0x4,%ymm0,%xmm8
+    0xc4,0xc3,0x7d,0x1d,0xc9,0x04,              //  vcvtps2ph     $0x4,%ymm1,%xmm9
+    0xc4,0xc3,0x7d,0x1d,0xd2,0x04,              //  vcvtps2ph     $0x4,%ymm2,%xmm10
+    0xc4,0xc3,0x7d,0x1d,0xdb,0x04,              //  vcvtps2ph     $0x4,%ymm3,%xmm11
+    0xc4,0x41,0x39,0x61,0xe1,                   //  vpunpcklwd    %xmm9,%xmm8,%xmm12
+    0xc4,0x41,0x39,0x69,0xc1,                   //  vpunpckhwd    %xmm9,%xmm8,%xmm8
+    0xc4,0x41,0x29,0x61,0xcb,                   //  vpunpcklwd    %xmm11,%xmm10,%xmm9
+    0xc4,0x41,0x29,0x69,0xd3,                   //  vpunpckhwd    %xmm11,%xmm10,%xmm10
+    0xc4,0x41,0x19,0x62,0xd9,                   //  vpunpckldq    %xmm9,%xmm12,%xmm11
+    0xc5,0x7a,0x7f,0x1c,0xf8,                   //  vmovdqu       %xmm11,(%rax,%rdi,8)
+    0xc4,0x41,0x19,0x6a,0xc9,                   //  vpunpckhdq    %xmm9,%xmm12,%xmm9
+    0xc5,0x7a,0x7f,0x4c,0xf8,0x10,              //  vmovdqu       %xmm9,0x10(%rax,%rdi,8)
+    0xc4,0x41,0x39,0x62,0xca,                   //  vpunpckldq    %xmm10,%xmm8,%xmm9
+    0xc5,0x7a,0x7f,0x4c,0xf8,0x20,              //  vmovdqu       %xmm9,0x20(%rax,%rdi,8)
+    0xc4,0x41,0x39,0x6a,0xc2,                   //  vpunpckhdq    %xmm10,%xmm8,%xmm8
+    0xc5,0x7a,0x7f,0x44,0xf8,0x30,              //  vmovdqu       %xmm8,0x30(%rax,%rdi,8)
+};
+
+#endif
+
+#endif//SkSplicer_generated_DEFINED
diff --git a/src/splicer/SkSplicer_shared.h b/src/splicer/SkSplicer_shared.h
new file mode 100644
index 0000000..1c530c4
--- /dev/null
+++ b/src/splicer/SkSplicer_shared.h
@@ -0,0 +1,43 @@
+/*
+ * 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 SkSplicer_shared_DEFINED
+#define SkSplicer_shared_DEFINED
+
+// This file contains definitions shared by SkSplicer.cpp (compiled normally as part of Skia)
+// and SkSplicer_stages.cpp (compiled offline into SkSplicer_generated.h).  Keep it simple!
+
+#include <stdint.h>
+
+// SkSplicer Stages can use constant literals only if they end up baked into the instruction,
+// like bit shifts and rounding modes.  Any other constant values must be pulled from this struct
+// (except 0 and 0.0f, which always end up as some sort of xor instruction).
+//
+// This constraint makes it much easier to move and reorder the code for each Stage.
+
+struct SkSplicer_constants {
+    uint32_t _0x000000ff;  //  0x000000ff
+    float    _1;           //  1.0f
+    float    _255;         //  255.0f
+    float    _1_255;       //  1/255.0f
+
+    // from_srgb
+    float    _00025;       //  0.0025f
+    float    _06975;       //  0.6975f
+    float    _03000;       //  0.3000f
+    float    _1_1292;      //  1/12.92f
+    float    _0055;        //  0.055f
+
+    // to_srgb
+    float    _1246;        //  12.46f
+    float    _0411192;     //  0.411192f
+    float    _0689206;     //  0.689206f
+    float   n_00988;       // -0.0988f
+    float    _00043;       //  0.0043f
+};
+
+#endif//SkSplicer_shared_DEFINED
diff --git a/src/splicer/SkSplicer_stages.cpp b/src/splicer/SkSplicer_stages.cpp
new file mode 100644
index 0000000..e3a19ea
--- /dev/null
+++ b/src/splicer/SkSplicer_stages.cpp
@@ -0,0 +1,309 @@
+/*
+ * 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 "SkSplicer_shared.h"
+#include <string.h>
+
+#if !defined(__clang__)
+    #error This file is not like the rest of Skia.  It must be compiled with clang.
+#endif
+
+// We have very specific inlining requirements.  It helps to just take total control.
+#define AI __attribute__((always_inline)) inline
+
+#if defined(__aarch64__)
+    #include <arm_neon.h>
+
+    // Since we know we're using Clang, we can use its vector extensions.
+    using F   = float    __attribute__((ext_vector_type(4)));
+    using I32 =  int32_t __attribute__((ext_vector_type(4)));
+    using U32 = uint32_t __attribute__((ext_vector_type(4)));
+    using U8  = uint8_t  __attribute__((ext_vector_type(4)));
+
+    // We polyfill a few routines that Clang doesn't build into ext_vector_types.
+    AI static U32 round(F v)                           { return vcvtnq_u32_f32(v);       }
+    AI static F   min(F a, F b)                        { return vminq_f32(a,b);          }
+    AI static F   max(F a, F b)                        { return vmaxq_f32(a,b);          }
+    AI static F   fma(F f, F m, F a)                   { return vfmaq_f32(a,f,m);        }
+    AI static F   rcp  (F v) { auto e = vrecpeq_f32 (v); return vrecpsq_f32 (v,e  ) * e; }
+    AI static F   rsqrt(F v) { auto e = vrsqrteq_f32(v); return vrsqrtsq_f32(v,e*e) * e; }
+    AI static F   if_then_else(I32 c, F t, F e)        { return vbslq_f32((U32)c,t,e);   }
+#else
+    #if !defined(__AVX2__) || !defined(__FMA__) || !defined(__F16C__)
+        #error On x86, compile with -mavx2 -mfma -mf16c.
+    #endif
+    #include <immintrin.h>
+
+    // These are __m256 and __m256i, but friendlier and strongly-typed.
+    using F   = float    __attribute__((ext_vector_type(8)));
+    using I32 =  int32_t __attribute__((ext_vector_type(8)));
+    using U32 = uint32_t __attribute__((ext_vector_type(8)));
+    using U8  = uint8_t  __attribute__((ext_vector_type(8)));
+
+    AI static U32 round(F v)                    { return _mm256_cvtps_epi32(v); }
+    AI static F   min(F a, F b)                 { return _mm256_min_ps  (a,b);  }
+    AI static F   max(F a, F b)                 { return _mm256_max_ps  (a,b);  }
+    AI static F   fma(F f, F m, F a)            { return _mm256_fmadd_ps(f,m,a);}
+    AI static F   rcp  (F v)                    { return _mm256_rcp_ps     (v); }
+    AI static F   rsqrt(F v)                    { return _mm256_rsqrt_ps   (v); }
+    AI static F   if_then_else(I32 c, F t, F e) { return _mm256_blendv_ps(e,t,c); }
+#endif
+
+AI static F   cast  (U32 v) { return __builtin_convertvector((I32)v, F);   }
+AI static U32 expand(U8  v) { return __builtin_convertvector(     v, U32); }
+
+// We'll be compiling this file to an object file, then extracting parts of it into
+// SkSplicer_generated.h.  It's easier to do if the function names are not C++ mangled.
+#define C extern "C"
+
+// Stages all fit a common interface that allows SkSplicer to splice them together.
+using K = const SkSplicer_constants;
+using Stage = void(size_t x, size_t limit, void* ctx, K* k, F,F,F,F, F,F,F,F);
+
+// Stage's arguments act as the working set of registers within the final spliced function.
+// Here's a little primer on the x86-64/aarch64 ABIs:
+//   x:         rdi/x0          x and limit work to drive the loop, see loop_start in SkSplicer.cpp.
+//   limit:     rsi/x1
+//   ctx:       rdx/x2          Look for set_ctx in SkSplicer.cpp to see how this works.
+//   k:         rcx/x3
+//   vectors:   ymm0-ymm7/v0-v7
+
+
+// done() is the key to this entire splicing strategy.
+//
+// It matches the signature of Stage, so all the registers are kept live.
+// Every Stage calls done() and so will end in a single jmp (i.e. tail-call) into done(),
+// which marks the point where we can splice one Stage onto the next.
+//
+// The lovely bit is that we don't have to define done(), just declare it.
+C void done(size_t, size_t, void*, K*, F,F,F,F, F,F,F,F);
+
+// This should feel familiar to anyone who's read SkRasterPipeline_opts.h.
+// It's just a convenience to make a valid, spliceable Stage, nothing magic.
+#define STAGE(name)                                                              \
+    AI static void name##_k(size_t x, size_t limit, void* ctx, K* k,             \
+                            F& r, F& g, F& b, F& a, F& dr, F& dg, F& db, F& da); \
+    C void name(size_t x, size_t limit, void* ctx, K* k,                         \
+                F r, F g, F b, F a, F dr, F dg, F db, F da) {                    \
+        name##_k(x,limit,ctx,k, r,g,b,a, dr,dg,db,da);                           \
+        done    (x,limit,ctx,k, r,g,b,a, dr,dg,db,da);                           \
+    }                                                                            \
+    AI static void name##_k(size_t x, size_t limit, void* ctx, K* k,             \
+                            F& r, F& g, F& b, F& a, F& dr, F& dg, F& db, F& da)
+
+// We can now define Stages!
+
+// Some things to keep in mind while writing Stages:
+//   - do not branch;                                       (i.e. avoid jmp)
+//   - do not call functions that don't inline;             (i.e. avoid call, ret, stack use)
+//   - do not use constant literals other than 0 and 0.0f.  (i.e. avoid rip relative addressing)
+//
+// Some things that should work fine:
+//   - 0 and 0.0f;
+//   - arithmetic;
+//   - functions of F and U32 that we've defined above;
+//   - temporary values;
+//   - lambdas;
+//   - memcpy() with a compile-time constant size argument.
+
+STAGE(clear) {
+    r = g = b = a = 0;
+}
+
+STAGE(plus) {
+    r = r + dr;
+    g = g + dg;
+    b = b + db;
+    a = a + da;
+}
+
+STAGE(srcover) {
+    auto A = k->_1 - a;
+    r = fma(dr, A, r);
+    g = fma(dg, A, g);
+    b = fma(db, A, b);
+    a = fma(db, A, a);
+}
+STAGE(dstover) { srcover_k(x,limit,ctx,k, dr,dg,db,da, r,g,b,a); }
+
+STAGE(clamp_0) {
+    r = max(r, 0);
+    g = max(g, 0);
+    b = max(b, 0);
+    a = max(a, 0);
+}
+
+STAGE(clamp_1) {
+    r = min(r, k->_1);
+    g = min(g, k->_1);
+    b = min(b, k->_1);
+    a = min(a, k->_1);
+}
+
+STAGE(clamp_a) {
+    a = min(a, k->_1);
+    r = min(r, a);
+    g = min(g, a);
+    b = min(b, a);
+}
+
+STAGE(swap) {
+    auto swap = [](F& v, F& dv) {
+        auto tmp = v;
+        v = dv;
+        dv = tmp;
+    };
+    swap(r, dr);
+    swap(g, dg);
+    swap(b, db);
+    swap(a, da);
+}
+STAGE(move_src_dst) {
+    dr = r;
+    dg = g;
+    db = b;
+    da = a;
+}
+STAGE(move_dst_src) {
+    r = dr;
+    g = dg;
+    b = db;
+    a = da;
+}
+
+STAGE(premul) {
+    r = r * a;
+    g = g * a;
+    b = b * a;
+}
+STAGE(unpremul) {
+    auto scale = if_then_else(a == 0, 0, k->_1 / a);
+    r = r * scale;
+    g = g * scale;
+    b = b * scale;
+}
+
+STAGE(from_srgb) {
+    auto fn = [&](F s) {
+        auto lo = s * k->_1_1292;
+        auto hi = fma(s*s, fma(s, k->_03000, k->_06975), k->_00025);
+        return if_then_else(s < k->_0055, lo, hi);
+    };
+    r = fn(r);
+    g = fn(g);
+    b = fn(b);
+}
+STAGE(to_srgb) {
+    auto fn = [&](F l) {
+        F sqrt = rcp  (rsqrt(l)),
+          ftrt = rsqrt(rsqrt(l));
+        auto lo = l * k->_1246;
+        auto hi = min(k->_1, fma(k->_0411192, ftrt,
+                             fma(k->_0689206, sqrt,
+                                 k->n_00988)));
+        return if_then_else(l < k->_00043, lo, hi);
+    };
+    r = fn(r);
+    g = fn(g);
+    b = fn(b);
+}
+
+STAGE(scale_u8) {
+    auto ptr = *(const uint8_t**)ctx + x;
+
+    U8 scales;
+    memcpy(&scales, ptr, sizeof(scales));
+    auto c = cast(expand(scales)) * k->_1_255;
+
+    r = r * c;
+    g = g * c;
+    b = b * c;
+    a = a * c;
+}
+
+STAGE(load_8888) {
+    auto ptr = *(const uint32_t**)ctx + x;
+
+    U32 px;
+    memcpy(&px, ptr, sizeof(px));
+
+    r = cast((px      ) & k->_0x000000ff) * k->_1_255;
+    g = cast((px >>  8) & k->_0x000000ff) * k->_1_255;
+    b = cast((px >> 16) & k->_0x000000ff) * k->_1_255;
+    a = cast((px >> 24)                 ) * k->_1_255;
+}
+
+STAGE(store_8888) {
+    auto ptr = *(uint32_t**)ctx + x;
+
+    U32 px = round(r * k->_255)
+           | round(g * k->_255) <<  8
+           | round(b * k->_255) << 16
+           | round(a * k->_255) << 24;
+    memcpy(ptr, &px, sizeof(px));
+}
+
+STAGE(load_f16) {
+    auto ptr = *(const uint64_t**)ctx + x;
+
+#if defined(__aarch64__)
+    auto halfs = vld4_f16((const float16_t*)ptr);
+    r = vcvt_f32_f16(halfs.val[0]);
+    g = vcvt_f32_f16(halfs.val[1]);
+    b = vcvt_f32_f16(halfs.val[2]);
+    a = vcvt_f32_f16(halfs.val[3]);
+#else
+    auto _01 = _mm_loadu_si128(((__m128i*)ptr) + 0),
+         _23 = _mm_loadu_si128(((__m128i*)ptr) + 1),
+         _45 = _mm_loadu_si128(((__m128i*)ptr) + 2),
+         _67 = _mm_loadu_si128(((__m128i*)ptr) + 3);
+
+    auto _02 = _mm_unpacklo_epi16(_01, _23),  // r0 r2 g0 g2 b0 b2 a0 a2
+         _13 = _mm_unpackhi_epi16(_01, _23),  // r1 r3 g1 g3 b1 b3 a1 a3
+         _46 = _mm_unpacklo_epi16(_45, _67),
+         _57 = _mm_unpackhi_epi16(_45, _67);
+
+    auto rg0123 = _mm_unpacklo_epi16(_02, _13),  // r0 r1 r2 r3 g0 g1 g2 g3
+         ba0123 = _mm_unpackhi_epi16(_02, _13),  // b0 b1 b2 b3 a0 a1 a2 a3
+         rg4567 = _mm_unpacklo_epi16(_46, _57),
+         ba4567 = _mm_unpackhi_epi16(_46, _57);
+
+    r = _mm256_cvtph_ps(_mm_unpacklo_epi64(rg0123, rg4567));
+    g = _mm256_cvtph_ps(_mm_unpackhi_epi64(rg0123, rg4567));
+    b = _mm256_cvtph_ps(_mm_unpacklo_epi64(ba0123, ba4567));
+    a = _mm256_cvtph_ps(_mm_unpackhi_epi64(ba0123, ba4567));
+#endif
+}
+
+STAGE(store_f16) {
+    auto ptr = *(uint64_t**)ctx + x;
+
+#if defined(__aarch64__)
+    float16x4x4_t halfs = {{
+        vcvt_f16_f32(r),
+        vcvt_f16_f32(g),
+        vcvt_f16_f32(b),
+        vcvt_f16_f32(a),
+    }};
+    vst4_f16((float16_t*)ptr, halfs);
+#else
+    auto R = _mm256_cvtps_ph(r, _MM_FROUND_CUR_DIRECTION),
+         G = _mm256_cvtps_ph(g, _MM_FROUND_CUR_DIRECTION),
+         B = _mm256_cvtps_ph(b, _MM_FROUND_CUR_DIRECTION),
+         A = _mm256_cvtps_ph(a, _MM_FROUND_CUR_DIRECTION);
+
+    auto rg0123 = _mm_unpacklo_epi16(R, G),  // r0 g0 r1 g1 r2 g2 r3 g3
+         rg4567 = _mm_unpackhi_epi16(R, G),  // r4 g4 r5 g5 r6 g6 r7 g7
+         ba0123 = _mm_unpacklo_epi16(B, A),
+         ba4567 = _mm_unpackhi_epi16(B, A);
+
+    _mm_storeu_si128((__m128i*)ptr + 0, _mm_unpacklo_epi32(rg0123, ba0123));
+    _mm_storeu_si128((__m128i*)ptr + 1, _mm_unpackhi_epi32(rg0123, ba0123));
+    _mm_storeu_si128((__m128i*)ptr + 2, _mm_unpacklo_epi32(rg4567, ba4567));
+    _mm_storeu_si128((__m128i*)ptr + 3, _mm_unpackhi_epi32(rg4567, ba4567));
+#endif
+}
diff --git a/src/splicer/build_stages.py b/src/splicer/build_stages.py
new file mode 100755
index 0000000..1a9813e
--- /dev/null
+++ b/src/splicer/build_stages.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python2.7
+#
+# 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 re
+import subprocess
+import sys
+
+cflags = '-std=c++11 -Os -fomit-frame-pointer'.split()
+
+hsw = '-mavx2 -mfma -mf16c'.split()
+subprocess.check_call(['clang++'] + cflags + hsw +
+                      ['-c', 'src/splicer/SkSplicer_stages.cpp'] +
+                      ['-o', 'hsw.o'])
+
+aarch64 = [
+    '--target=aarch64-linux-android',
+    '--sysroot=' +
+    '/Users/mtklein/brew/opt/android-ndk/platforms/android-21/arch-arm64',
+]
+subprocess.check_call(['clang++'] + cflags + aarch64 +
+                      ['-c', 'src/splicer/SkSplicer_stages.cpp'] +
+                      ['-o', 'aarch64.o'])
+
+def parse_object_file(dot_o, array_type, done):
+  for line in subprocess.check_output(['gobjdump', '-d', dot_o]).split('\n'):
+    line = line.strip()
+    if not line or line.startswith(dot_o) or line.startswith('Disassembly'):
+      continue
+
+    # E.g. 00000000000003a4 <_load_f16>:
+    m = re.match('''................ <_?(.*)>:''', line)
+    if m:
+      print 'static const', array_type, 'kSplice_' + m.group(1) + '[] = {'
+      continue
+
+    columns = line.split('\t')
+    code = columns[1]
+    if len(columns) == 4:
+      inst = columns[2]
+      args = columns[3]
+    else:
+      inst, args = columns[2].split(' ', 1)
+    code, inst, args = code.strip(), inst.strip(), args.strip()
+
+    # We can't splice code that uses ip-relative addressing.
+    for arg in args:
+      assert 'rip' not in arg  # TODO: detect on aarch64 too
+
+    if code == done:
+      print '};'
+      continue
+
+    hexed = ''.join('0x'+x+',' for x in code.split(' '))
+    print '    ' + hexed + ' '*(44-len(hexed)) + \
+          '//  ' + inst  + ' '*(14-len(inst))  + args
+
+print '''/*
+ * 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 SkSplicer_generated_DEFINED
+#define SkSplicer_generated_DEFINED
+
+// This file is generated semi-automatically with this command:
+//   $ src/splicer/build_stages.py > src/splicer/SkSplicer_generated.h
+
+#if defined(__aarch64__)
+'''
+parse_object_file('aarch64.o', 'unsigned int', '14000000')
+print '\n#else\n'
+parse_object_file('hsw.o', 'unsigned char', 'e9 00 00 00 00')
+print '\n#endif\n'
+print '#endif//SkSplicer_generated_DEFINED'
diff --git a/src/utils/mac/SkCreateCGImageRef.cpp b/src/utils/mac/SkCreateCGImageRef.cpp
index 1b7e12b..e373592 100644
--- a/src/utils/mac/SkCreateCGImageRef.cpp
+++ b/src/utils/mac/SkCreateCGImageRef.cpp
@@ -178,6 +178,30 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+CGContextRef SkCreateCGContext(const SkPixmap& pmap) {
+    CGBitmapInfo cg_bitmap_info = 0;
+    size_t bitsPerComponent = 0;
+    switch (pmap.colorType()) {
+        case kRGBA_8888_SkColorType:
+            bitsPerComponent = 8;
+            cg_bitmap_info = ComputeCGAlphaInfo_RGBA(pmap.alphaType());
+            break;
+        case kBGRA_8888_SkColorType:
+            bitsPerComponent = 8;
+            cg_bitmap_info = ComputeCGAlphaInfo_BGRA(pmap.alphaType());
+            break;
+        default:
+            return nullptr;   // no other colortypes are supported (for now)
+    }
+
+    size_t rb = pmap.addr() ? pmap.rowBytes() : 0;
+    CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+    CGContextRef cg = CGBitmapContextCreate(pmap.writable_addr(), pmap.width(), pmap.height(),
+                                            bitsPerComponent, rb, cs, cg_bitmap_info);
+    CFRelease(cs);
+    return cg;
+}
+
 SK_API bool SkCopyPixelsFromCGImage(const SkImageInfo& info, size_t rowBytes, void* pixels,
                                     CGImageRef image) {
     CGBitmapInfo cg_bitmap_info = 0;
diff --git a/src/xml/SkDOM.cpp b/src/xml/SkDOM.cpp
index 38ba669..b1cf1d5 100644
--- a/src/xml/SkDOM.cpp
+++ b/src/xml/SkDOM.cpp
@@ -8,31 +8,32 @@
 
 #include "SkDOM.h"
 #include "SkStream.h"
+#include "SkXMLParser.h"
 #include "SkXMLWriter.h"
 
-/////////////////////////////////////////////////////////////////////////
-
-#include "SkXMLParser.h"
-bool SkXMLParser::parse(const SkDOM& dom, const SkDOMNode* node)
-{
+bool SkXMLParser::parse(const SkDOM& dom, const SkDOMNode* node) {
     const char* elemName = dom.getName(node);
 
-    if (this->startElement(elemName))
+    if (this->startElement(elemName)) {
         return false;
+    }
 
     SkDOM::AttrIter iter(dom, node);
     const char*     name, *value;
 
-    while ((name = iter.next(&value)) != nullptr)
-        if (this->addAttribute(name, value))
+    while ((name = iter.next(&value)) != nullptr) {
+        if (this->addAttribute(name, value)) {
             return false;
+        }
+    }
 
-    if ((node = dom.getFirstChild(node)) != nullptr)
+    if ((node = dom.getFirstChild(node)) != nullptr) {
         do {
-            if (!this->parse(dom, node))
+            if (!this->parse(dom, node)) {
                 return false;
+            }
         } while ((node = dom.getNextSibling(node)) != nullptr);
-
+    }
     return !this->endElement(elemName);
 }
 
@@ -51,12 +52,11 @@
     uint8_t     fType;
     uint8_t     fPad;
 
-    const SkDOMAttr* attrs() const
-    {
+    const SkDOMAttr* attrs() const {
         return (const SkDOMAttr*)(this + 1);
     }
-    SkDOMAttr* attrs()
-    {
+
+    SkDOMAttr* attrs() {
         return (SkDOMAttr*)(this + 1);
     }
 };
@@ -65,68 +65,60 @@
 
 #define kMinChunkSize   512
 
-SkDOM::SkDOM() : fAlloc(kMinChunkSize), fRoot(nullptr)
-{
-}
+SkDOM::SkDOM() : fAlloc(kMinChunkSize), fRoot(nullptr) {}
 
-SkDOM::~SkDOM()
-{
-}
+SkDOM::~SkDOM() {}
 
-const SkDOM::Node* SkDOM::getRootNode() const
-{
+const SkDOM::Node* SkDOM::getRootNode() const {
     return fRoot;
 }
 
-const SkDOM::Node* SkDOM::getFirstChild(const Node* node, const char name[]) const
-{
+const SkDOM::Node* SkDOM::getFirstChild(const Node* node, const char name[]) const {
     SkASSERT(node);
     const Node* child = node->fFirstChild;
 
-    if (name)
-    {
-        for (; child != nullptr; child = child->fNextSibling)
-            if (!strcmp(name, child->fName))
+    if (name) {
+        for (; child != nullptr; child = child->fNextSibling) {
+            if (!strcmp(name, child->fName)) {
                 break;
+            }
+        }
     }
     return child;
 }
 
-const SkDOM::Node* SkDOM::getNextSibling(const Node* node, const char name[]) const
-{
+const SkDOM::Node* SkDOM::getNextSibling(const Node* node, const char name[]) const {
     SkASSERT(node);
     const Node* sibling = node->fNextSibling;
-    if (name)
-    {
-        for (; sibling != nullptr; sibling = sibling->fNextSibling)
-            if (!strcmp(name, sibling->fName))
+    if (name) {
+        for (; sibling != nullptr; sibling = sibling->fNextSibling) {
+            if (!strcmp(name, sibling->fName)) {
                 break;
+            }
+        }
     }
     return sibling;
 }
 
-SkDOM::Type SkDOM::getType(const Node* node) const
-{
+SkDOM::Type SkDOM::getType(const Node* node) const {
     SkASSERT(node);
     return (Type)node->fType;
 }
 
-const char* SkDOM::getName(const Node* node) const
-{
+const char* SkDOM::getName(const Node* node) const {
     SkASSERT(node);
     return node->fName;
 }
 
-const char* SkDOM::findAttr(const Node* node, const char name[]) const
-{
+const char* SkDOM::findAttr(const Node* node, const char name[]) const {
     SkASSERT(node);
     const Attr* attr = node->attrs();
     const Attr* stop = attr + node->fAttrCount;
 
-    while (attr < stop)
-    {
-        if (!strcmp(attr->fName, name))
+    while (attr < stop) {
+        if (!strcmp(attr->fName, name)) {
             return attr->fValue;
+        }
         attr += 1;
     }
     return nullptr;
@@ -134,28 +126,25 @@
 
 /////////////////////////////////////////////////////////////////////////////////////
 
-const SkDOM::Attr* SkDOM::getFirstAttr(const Node* node) const
-{
+const SkDOM::Attr* SkDOM::getFirstAttr(const Node* node) const {
     return node->fAttrCount ? node->attrs() : nullptr;
 }
 
-const SkDOM::Attr* SkDOM::getNextAttr(const Node* node, const Attr* attr) const
-{
+const SkDOM::Attr* SkDOM::getNextAttr(const Node* node, const Attr* attr) const {
     SkASSERT(node);
-    if (attr == nullptr)
+    if (attr == nullptr) {
         return nullptr;
+    }
     return (attr - node->attrs() + 1) < node->fAttrCount ? attr + 1 : nullptr;
 }
 
-const char* SkDOM::getAttrName(const Node* node, const Attr* attr) const
-{
+const char* SkDOM::getAttrName(const Node* node, const Attr* attr) const {
     SkASSERT(node);
     SkASSERT(attr);
     return attr->fName;
 }
 
-const char* SkDOM::getAttrValue(const Node* node, const Attr* attr) const
-{
+const char* SkDOM::getAttrValue(const Node* node, const Attr* attr) const {
     SkASSERT(node);
     SkASSERT(attr);
     return attr->fValue;
@@ -163,19 +152,16 @@
 
 /////////////////////////////////////////////////////////////////////////////////////
 
-SkDOM::AttrIter::AttrIter(const SkDOM&, const SkDOM::Node* node)
-{
+SkDOM::AttrIter::AttrIter(const SkDOM&, const SkDOM::Node* node) {
     SkASSERT(node);
     fAttr = node->attrs();
     fStop = fAttr + node->fAttrCount;
 }
 
-const char* SkDOM::AttrIter::next(const char** value)
-{
+const char* SkDOM::AttrIter::next(const char** value) {
     const char* name = nullptr;
 
-    if (fAttr < fStop)
-    {
+    if (fAttr < fStop) {
         name = fAttr->fName;
         if (value)
             *value = fAttr->fValue;
@@ -189,8 +175,7 @@
 #include "SkXMLParser.h"
 #include "SkTDArray.h"
 
-static char* dupstr(SkChunkAlloc* chunk, const char src[])
-{
+static char* dupstr(SkChunkAlloc* chunk, const char src[]) {
     SkASSERT(chunk && src);
     size_t  len = strlen(src);
     char*   dst = (char*)chunk->alloc(len + 1, SkChunkAlloc::kThrow_AllocFailType);
@@ -200,8 +185,7 @@
 
 class SkDOMParser : public SkXMLParser {
 public:
-    SkDOMParser(SkChunkAlloc* chunk) : SkXMLParser(&fParserError), fAlloc(chunk)
-    {
+    SkDOMParser(SkChunkAlloc* chunk) : SkXMLParser(&fParserError), fAlloc(chunk) {
         fAlloc->reset();
         fRoot = nullptr;
         fLevel = 0;
@@ -211,8 +195,7 @@
     SkXMLParserError fParserError;
 
 protected:
-    void flushAttributes()
-    {
+    void flushAttributes() {
         SkASSERT(fLevel > 0);
 
         int attrCount = fAttrs.count();
@@ -225,13 +208,10 @@
         node->fAttrCount = SkToU16(attrCount);
         node->fType = fElemType;
 
-        if (fRoot == nullptr)
-        {
+        if (fRoot == nullptr) {
             node->fNextSibling = nullptr;
             fRoot = node;
-        }
-        else    // this adds siblings in reverse order. gets corrected in onEndElement()
-        {
+        } else { // this adds siblings in reverse order. gets corrected in onEndElement()
             SkDOM::Node* parent = fParentStack.top();
             SkASSERT(fRoot && parent);
             node->fNextSibling = parent->fFirstChild;
@@ -268,8 +248,7 @@
 
         SkDOM::Node* child = parent->fFirstChild;
         SkDOM::Node* prev = nullptr;
-        while (child)
-        {
+        while (child) {
             SkDOM::Node* next = child->fNextSibling;
             child->fNextSibling = prev;
             prev = child;
@@ -289,9 +268,9 @@
 
 private:
     void startCommon(const char elem[], SkDOM::Type type) {
-        if (fLevel > 0 && fNeedToFlush)
+        if (fLevel > 0 && fNeedToFlush) {
             this->flushAttributes();
-
+        }
         fNeedToFlush = true;
         fElemName = dupstr(fAlloc, elem);
         fElemType = type;
@@ -325,8 +304,7 @@
 
 ///////////////////////////////////////////////////////////////////////////
 
-static void walk_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLParser* parser)
-{
+static void walk_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLParser* parser) {
     const char* elem = dom.getName(node);
     if (dom.getType(node) == SkDOM::kText_Type) {
         SkASSERT(dom.countChildren(node) == 0);
@@ -352,8 +330,7 @@
     parser->endElement(elem);
 }
 
-const SkDOM::Node* SkDOM::copy(const SkDOM& dom, const SkDOM::Node* node)
-{
+const SkDOM::Node* SkDOM::copy(const SkDOM& dom, const SkDOM::Node* node) {
     SkDOMParser parser(&fAlloc);
 
     walk_dom(dom, node, &parser);
@@ -379,13 +356,11 @@
 
 //////////////////////////////////////////////////////////////////////////
 
-int SkDOM::countChildren(const Node* node, const char elem[]) const
-{
+int SkDOM::countChildren(const Node* node, const char elem[]) const {
     int count = 0;
 
     node = this->getFirstChild(node, elem);
-    while (node)
-    {
+    while (node) {
         count += 1;
         node = this->getNextSibling(node, elem);
     }
@@ -396,82 +371,56 @@
 
 #include "SkParse.h"
 
-bool SkDOM::findS32(const Node* node, const char name[], int32_t* value) const
-{
+bool SkDOM::findS32(const Node* node, const char name[], int32_t* value) const {
     const char* vstr = this->findAttr(node, name);
     return vstr && SkParse::FindS32(vstr, value);
 }
 
-bool SkDOM::findScalars(const Node* node, const char name[], SkScalar value[], int count) const
-{
+bool SkDOM::findScalars(const Node* node, const char name[], SkScalar value[], int count) const {
     const char* vstr = this->findAttr(node, name);
     return vstr && SkParse::FindScalars(vstr, value, count);
 }
 
-bool SkDOM::findHex(const Node* node, const char name[], uint32_t* value) const
-{
+bool SkDOM::findHex(const Node* node, const char name[], uint32_t* value) const {
     const char* vstr = this->findAttr(node, name);
     return vstr && SkParse::FindHex(vstr, value);
 }
 
-bool SkDOM::findBool(const Node* node, const char name[], bool* value) const
-{
+bool SkDOM::findBool(const Node* node, const char name[], bool* value) const {
     const char* vstr = this->findAttr(node, name);
     return vstr && SkParse::FindBool(vstr, value);
 }
 
-int SkDOM::findList(const Node* node, const char name[], const char list[]) const
-{
+int SkDOM::findList(const Node* node, const char name[], const char list[]) const {
     const char* vstr = this->findAttr(node, name);
     return vstr ? SkParse::FindList(vstr, list) : -1;
 }
 
-bool SkDOM::hasAttr(const Node* node, const char name[], const char value[]) const
-{
+bool SkDOM::hasAttr(const Node* node, const char name[], const char value[]) const {
     const char* vstr = this->findAttr(node, name);
     return vstr && !strcmp(vstr, value);
 }
 
-bool SkDOM::hasS32(const Node* node, const char name[], int32_t target) const
-{
+bool SkDOM::hasS32(const Node* node, const char name[], int32_t target) const {
     const char* vstr = this->findAttr(node, name);
     int32_t     value;
     return vstr && SkParse::FindS32(vstr, &value) && value == target;
 }
 
-bool SkDOM::hasScalar(const Node* node, const char name[], SkScalar target) const
-{
+bool SkDOM::hasScalar(const Node* node, const char name[], SkScalar target) const {
     const char* vstr = this->findAttr(node, name);
     SkScalar    value;
     return vstr && SkParse::FindScalar(vstr, &value) && value == target;
 }
 
-bool SkDOM::hasHex(const Node* node, const char name[], uint32_t target) const
-{
+bool SkDOM::hasHex(const Node* node, const char name[], uint32_t target) const {
     const char* vstr = this->findAttr(node, name);
     uint32_t    value;
     return vstr && SkParse::FindHex(vstr, &value) && value == target;
 }
 
-bool SkDOM::hasBool(const Node* node, const char name[], bool target) const
-{
+bool SkDOM::hasBool(const Node* node, const char name[], bool target) const {
     const char* vstr = this->findAttr(node, name);
     bool        value;
     return vstr && SkParse::FindBool(vstr, &value) && value == target;
 }
-
-//////////////////////////////////////////////////////////////////////////
-
-#ifdef SK_DEBUG
-
-void SkDOM::dump(const Node* node, int level) const
-{
-    if (node == nullptr)
-        node = this->getRootNode();
-
-    SkDebugWStream debugStream;
-    SkXMLStreamWriter xmlWriter(&debugStream);
-    xmlWriter.writeDOM(*this, node, false);
-}
-
-#endif
diff --git a/src/xml/SkXMLWriter.cpp b/src/xml/SkXMLWriter.cpp
index 5ee237f..3a2c3d4 100644
--- a/src/xml/SkXMLWriter.cpp
+++ b/src/xml/SkXMLWriter.cpp
@@ -9,41 +9,35 @@
 #include "SkStream.h"
 
 SkXMLWriter::SkXMLWriter(bool doEscapeMarkup) : fDoEscapeMarkup(doEscapeMarkup)
-{
-}
+{}
 
-SkXMLWriter::~SkXMLWriter()
-{
+SkXMLWriter::~SkXMLWriter() {
     SkASSERT(fElems.count() == 0);
 }
 
-void SkXMLWriter::flush()
-{
-    while (fElems.count())
+void SkXMLWriter::flush() {
+    while (fElems.count()) {
         this->endElement();
+    }
 }
 
-void SkXMLWriter::addAttribute(const char name[], const char value[])
-{
+void SkXMLWriter::addAttribute(const char name[], const char value[]) {
     this->addAttributeLen(name, value, strlen(value));
 }
 
-void SkXMLWriter::addS32Attribute(const char name[], int32_t value)
-{
+void SkXMLWriter::addS32Attribute(const char name[], int32_t value) {
     SkString    tmp;
     tmp.appendS32(value);
     this->addAttribute(name, tmp.c_str());
 }
 
-void SkXMLWriter::addHexAttribute(const char name[], uint32_t value, int minDigits)
-{
+void SkXMLWriter::addHexAttribute(const char name[], uint32_t value, int minDigits) {
     SkString    tmp("0x");
     tmp.appendHex(value, minDigits);
     this->addAttribute(name, tmp.c_str());
 }
 
-void SkXMLWriter::addScalarAttribute(const char name[], SkScalar value)
-{
+void SkXMLWriter::addScalarAttribute(const char name[], SkScalar value) {
     SkString    tmp;
     tmp.appendScalar(value);
     this->addAttribute(name, tmp.c_str());
@@ -59,42 +53,37 @@
     fElems.top()->fHasText = true;
 }
 
-void SkXMLWriter::doEnd(Elem* elem)
-{
+void SkXMLWriter::doEnd(Elem* elem) {
     delete elem;
 }
 
-bool SkXMLWriter::doStart(const char name[], size_t length)
-{
+bool SkXMLWriter::doStart(const char name[], size_t length) {
     int level = fElems.count();
     bool firstChild = level > 0 && !fElems[level-1]->fHasChildren;
-    if (firstChild)
+    if (firstChild) {
         fElems[level-1]->fHasChildren = true;
+    }
     Elem** elem = fElems.push();
     *elem = new Elem(name, length);
     return firstChild;
 }
 
-SkXMLWriter::Elem* SkXMLWriter::getEnd()
-{
+SkXMLWriter::Elem* SkXMLWriter::getEnd() {
     Elem* elem;
     fElems.pop(&elem);
     return elem;
 }
 
-const char* SkXMLWriter::getHeader()
-{
+const char* SkXMLWriter::getHeader() {
     static const char gHeader[] = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
     return gHeader;
 }
 
-void SkXMLWriter::startElement(const char name[])
-{
+void SkXMLWriter::startElement(const char name[]) {
     this->startElementLen(name, strlen(name));
 }
 
-static const char* escape_char(char c, char storage[2])
-{
+static const char* escape_char(char c, char storage[2]) {
     static const char* gEscapeChars[] = {
         "<&lt;",
         ">&gt;",
@@ -104,29 +93,26 @@
     };
 
     const char** array = gEscapeChars;
-    for (unsigned i = 0; i < SK_ARRAY_COUNT(gEscapeChars); i++)
-    {
-        if (array[i][0] == c)
+    for (unsigned i = 0; i < SK_ARRAY_COUNT(gEscapeChars); i++) {
+        if (array[i][0] == c) {
             return &array[i][1];
+        }
     }
     storage[0] = c;
     storage[1] = 0;
     return storage;
 }
 
-static size_t escape_markup(char dst[], const char src[], size_t length)
-{
+static size_t escape_markup(char dst[], const char src[], size_t length) {
     size_t      extra = 0;
     const char* stop = src + length;
 
-    while (src < stop)
-    {
+    while (src < stop) {
         char        orig[2];
         const char* seq = escape_char(*src, orig);
         size_t      seqSize = strlen(seq);
 
-        if (dst)
-        {
+        if (dst) {
             memcpy(dst, seq, seqSize);
             dst += seqSize;
         }
@@ -140,15 +126,12 @@
     return extra;
 }
 
-void SkXMLWriter::addAttributeLen(const char name[], const char value[], size_t length)
-{
+void SkXMLWriter::addAttributeLen(const char name[], const char value[], size_t length) {
     SkString valueStr;
 
-    if (fDoEscapeMarkup)
-    {
+    if (fDoEscapeMarkup) {
         size_t   extra = escape_markup(nullptr, value, length);
-        if (extra)
-        {
+        if (extra) {
             valueStr.resize(length + extra);
             (void)escape_markup(valueStr.writable_str(), value, length);
             value = valueStr.c_str();
@@ -158,17 +141,14 @@
     this->onAddAttributeLen(name, value, length);
 }
 
-void SkXMLWriter::startElementLen(const char elem[], size_t length)
-{
+void SkXMLWriter::startElementLen(const char elem[], size_t length) {
     this->onStartElementLen(elem, length);
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////
 
-static void write_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLWriter* w, bool skipRoot)
-{
-    if (!skipRoot)
-    {
+static void write_dom(const SkDOM& dom, const SkDOM::Node* node, SkXMLWriter* w, bool skipRoot) {
+    if (!skipRoot) {
         const char* elem = dom.getName(node);
         if (dom.getType(node) == SkDOM::kText_Type) {
             SkASSERT(dom.countChildren(node) == 0);
@@ -181,50 +161,47 @@
         SkDOM::AttrIter iter(dom, node);
         const char* name;
         const char* value;
-        while ((name = iter.next(&value)) != nullptr)
+        while ((name = iter.next(&value)) != nullptr) {
             w->addAttribute(name, value);
+        }
     }
 
     node = dom.getFirstChild(node, nullptr);
-    while (node)
-    {
+    while (node) {
         write_dom(dom, node, w, false);
         node = dom.getNextSibling(node, nullptr);
     }
 
-    if (!skipRoot)
+    if (!skipRoot) {
         w->endElement();
+    }
 }
 
-void SkXMLWriter::writeDOM(const SkDOM& dom, const SkDOM::Node* node, bool skipRoot)
-{
-    if (node)
+void SkXMLWriter::writeDOM(const SkDOM& dom, const SkDOM::Node* node, bool skipRoot) {
+    if (node) {
         write_dom(dom, node, this, skipRoot);
+    }
 }
 
 void SkXMLWriter::writeHeader()
-{
-}
+{}
 
 // SkXMLStreamWriter
 
-static void tab(SkWStream& stream, int level)
-{
-    for (int i = 0; i < level; i++)
+static void tab(SkWStream& stream, int level) {
+    for (int i = 0; i < level; i++) {
         stream.writeText("\t");
+    }
 }
 
 SkXMLStreamWriter::SkXMLStreamWriter(SkWStream* stream) : fStream(*stream)
-{
-}
+{}
 
-SkXMLStreamWriter::~SkXMLStreamWriter()
-{
+SkXMLStreamWriter::~SkXMLStreamWriter() {
     this->flush();
 }
 
-void SkXMLStreamWriter::onAddAttributeLen(const char name[], const char value[], size_t length)
-{
+void SkXMLStreamWriter::onAddAttributeLen(const char name[], const char value[], size_t length) {
     SkASSERT(!fElems.top()->fHasChildren && !fElems.top()->fHasText);
     fStream.writeText(" ");
     fStream.writeText(name);
@@ -246,11 +223,9 @@
     fStream.newline();
 }
 
-void SkXMLStreamWriter::onEndElement()
-{
+void SkXMLStreamWriter::onEndElement() {
     Elem* elem = getEnd();
-    if (elem->fHasChildren || elem->fHasText)
-    {
+    if (elem->fHasChildren || elem->fHasText) {
         tab(fStream, fElems.count());
         fStream.writeText("</");
         fStream.writeText(elem->fName.c_str());
@@ -262,11 +237,9 @@
     doEnd(elem);
 }
 
-void SkXMLStreamWriter::onStartElementLen(const char name[], size_t length)
-{
+void SkXMLStreamWriter::onStartElementLen(const char name[], size_t length) {
     int level = fElems.count();
-    if (this->doStart(name, length))
-    {
+    if (this->doStart(name, length)) {
         // the first child, need to close with >
         fStream.writeText(">");
         fStream.newline();
@@ -277,8 +250,7 @@
     fStream.write(name, length);
 }
 
-void SkXMLStreamWriter::writeHeader()
-{
+void SkXMLStreamWriter::writeHeader() {
     const char* header = getHeader();
     fStream.write(header, strlen(header));
     fStream.newline();
@@ -293,13 +265,11 @@
 {
 }
 
-SkXMLParserWriter::~SkXMLParserWriter()
-{
+SkXMLParserWriter::~SkXMLParserWriter() {
     this->flush();
 }
 
-void SkXMLParserWriter::onAddAttributeLen(const char name[], const char value[], size_t length)
-{
+void SkXMLParserWriter::onAddAttributeLen(const char name[], const char value[], size_t length) {
     SkASSERT(fElems.count() == 0 || (!fElems.top()->fHasChildren && !fElems.top()->fHasText));
     SkString str(value, length);
     fParser.addAttribute(name, str.c_str());
@@ -309,53 +279,14 @@
     fParser.text(text, SkToInt(length));
 }
 
-void SkXMLParserWriter::onEndElement()
-{
+void SkXMLParserWriter::onEndElement() {
     Elem* elem = this->getEnd();
     fParser.endElement(elem->fName.c_str());
     this->doEnd(elem);
 }
 
-void SkXMLParserWriter::onStartElementLen(const char name[], size_t length)
-{
+void SkXMLParserWriter::onStartElementLen(const char name[], size_t length) {
     (void)this->doStart(name, length);
     SkString str(name, length);
     fParser.startElement(str.c_str());
 }
-
-
-////////////////////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////////////
-
-#ifdef SK_DEBUG
-
-void SkXMLStreamWriter::UnitTest()
-{
-#ifdef SK_SUPPORT_UNITTEST
-    SkDebugWStream  s;
-    SkXMLStreamWriter       w(&s);
-
-    w.startElement("elem0");
-    w.addAttribute("hello", "world");
-    w.addS32Attribute("dec", 42);
-    w.addHexAttribute("hex", 0x42, 3);
-    w.addScalarAttribute("scalar", -4.2f);
-    w.startElement("elem1");
-        w.endElement();
-        w.startElement("elem1");
-        w.addAttribute("name", "value");
-        w.endElement();
-        w.startElement("elem1");
-            w.startElement("elem2");
-                w.startElement("elem3");
-                w.addAttribute("name", "value");
-                w.endElement();
-            w.endElement();
-            w.startElement("elem2");
-            w.endElement();
-        w.endElement();
-    w.endElement();
-#endif
-}
-
-#endif
diff --git a/tests/ClipStackTest.cpp b/tests/ClipStackTest.cpp
index bb5cc4a..a85f016 100644
--- a/tests/ClipStackTest.cpp
+++ b/tests/ClipStackTest.cpp
@@ -13,7 +13,9 @@
 #include "SkRegion.h"
 
 #if SK_SUPPORT_GPU
+#include "GrClipStackClip.h"
 #include "GrReducedClip.h"
+#include "GrResourceCache.h"
 typedef GrReducedClip::ElementList ElementList;
 typedef GrReducedClip::InitialState InitialState;
 #endif
@@ -1407,3 +1409,51 @@
     test_reduced_clip_stack_aa(reporter);
 #endif
 }
+
+//////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+sk_sp<GrTexture> GrClipStackClip::testingOnly_createClipMask(GrContext* context) const {
+    const GrReducedClip reducedClip(*fStack, SkRect::MakeWH(512, 512), 0);
+    return this->createSoftwareClipMask(context, reducedClip);
+}
+
+// Verify that clip masks are freed up when the clip state that generated them goes away.
+DEF_GPUTEST_FOR_ALL_CONTEXTS(ClipMaskCache, reporter, ctxInfo) {
+    // This test uses resource key tags which only function in debug builds.
+#ifdef SK_DEBUG
+    GrContext* context = ctxInfo.grContext();
+    SkClipStack stack;
+
+    SkPath path;
+    path.addCircle(10, 10, 8);
+    path.addCircle(15, 15, 8);
+    path.setFillType(SkPath::kEvenOdd_FillType);
+
+    static const char* kTag = GrClipStackClip::kMaskTestTag;
+    GrResourceCache* cache = context->getResourceCache();
+
+    static constexpr int kN = 5;
+
+    for (int i = 0; i < kN; ++i) {
+        SkMatrix m;
+        m.setTranslate(0.5, 0.5);
+        stack.save();
+        stack.clipPath(path, m, SkClipOp::kIntersect, true);
+        auto mask = GrClipStackClip(&stack).testingOnly_createClipMask(context);
+        REPORTER_ASSERT(reporter, 0 == strcmp(mask->getUniqueKey().tag(), kTag));
+        // Make sure mask isn't pinned in cache.
+        mask.reset(nullptr);
+        context->flush();
+        REPORTER_ASSERT(reporter, i + 1 == cache->countUniqueKeysWithTag(kTag));
+    }
+
+    for (int i = 0; i < kN; ++i) {
+        stack.restore();
+        cache->purgeAsNeeded();
+        REPORTER_ASSERT(reporter, kN - (i + 1) == cache->countUniqueKeysWithTag(kTag));
+    }
+#endif
+}
+
+#endif
diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp
index 762cd47..9f9a160 100644
--- a/tests/CodecAnimTest.cpp
+++ b/tests/CodecAnimTest.cpp
@@ -7,6 +7,9 @@
 
 #include "SkBitmap.h"
 #include "SkCodec.h"
+#include "SkCommonFlags.h"
+#include "SkImageEncoder.h"
+#include "SkOSPath.h"
 #include "SkStream.h"
 
 #include "Resources.h"
@@ -15,6 +18,19 @@
 #include <initializer_list>
 #include <vector>
 
+static void write_bm(const char* name, const SkBitmap& bm) {
+    if (FLAGS_writePath.isEmpty()) {
+        return;
+    }
+
+    SkString filename = SkOSPath::Join(FLAGS_writePath[0], name);
+    filename.appendf(".png");
+    SkFILEWStream file(filename.c_str());
+    if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) {
+        SkDebugf("failed to write '%s'\n", filename.c_str());
+    }
+}
+
 DEF_TEST(Codec_frames, r) {
     static const struct {
         const char*         fName;
@@ -27,6 +43,13 @@
         std::vector<size_t> fDurations;
         int                 fRepetitionCount;
     } gRecs[] = {
+        { "randPixelsAnim.gif", 13,
+            // required frames
+            { SkCodec::kNone, 1, 2, 3, 4, 4, 6, 7, 7, 7, 7, 7 },
+            // durations
+            { 0, 1000, 170, 40, 220, 7770, 90, 90, 90, 90, 90, 90, 90 },
+            // repetition count
+            0 },
         { "box.gif", 1, {}, {}, 0 },
         { "color_wheel.gif", 1, {}, {}, 0 },
         { "test640x479.gif", 4, { 0, 1, 2 }, { 200, 200, 200, 200 },
@@ -87,7 +110,10 @@
         // From here on, we are only concerned with animated images.
         REPORTER_ASSERT(r, frameInfos[0].fRequiredFrame == SkCodec::kNone);
         for (size_t i = 1; i < frameCount; i++) {
-            REPORTER_ASSERT(r, rec.fRequiredFrames[i-1] == frameInfos[i].fRequiredFrame);
+            if (rec.fRequiredFrames[i-1] != frameInfos[i].fRequiredFrame) {
+                ERRORF(r, "%s's frame %i has wrong dependency! expected: %i\tactual: %i",
+                       rec.fName, i, rec.fRequiredFrames[i-1], frameInfos[i].fRequiredFrame);
+            }
         }
 
         // Compare decoding in two ways:
@@ -132,6 +158,10 @@
                 SkASSERT(uncachedAddr != nullptr);
                 const bool lineMatches = memcmp(cachedAddr, uncachedAddr, rowLen) == 0;
                 if (!lineMatches) {
+                    SkString name = SkStringPrintf("cached_%i", i);
+                    write_bm(name.c_str(), cachedFrame);
+                    name = SkStringPrintf("uncached_%i", i);
+                    write_bm(name.c_str(), uncachedFrame);
                     ERRORF(r, "%s's frame %i is different depending on caching!", rec.fName, i);
                     break;
                 }
@@ -145,7 +175,10 @@
         }
 
         for (size_t i = 0; i < frameCount; i++) {
-            REPORTER_ASSERT(r, rec.fDurations[i] == frameInfos[i].fDuration);
+            if (rec.fDurations[i] != frameInfos[i].fDuration) {
+                ERRORF(r, "%s frame %i's durations do not match! expected: %i\tactual: %i",
+                       rec.fName, i, rec.fDurations[i], frameInfos[i].fDuration);
+            }
         }
     }
 }
diff --git a/tests/CodecTest.cpp b/tests/CodecTest.cpp
index 01e9cc4..0f6d54c 100644
--- a/tests/CodecTest.cpp
+++ b/tests/CodecTest.cpp
@@ -8,18 +8,19 @@
 #include "FakeStreams.h"
 #include "Resources.h"
 #include "SkAndroidCodec.h"
+#include "SkAutoMalloc.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
 #include "SkCodecImageGenerator.h"
 #include "SkColorSpace_XYZ.h"
 #include "SkData.h"
-#include "SkImageEncoder.h"
 #include "SkFrontBufferedStream.h"
+#include "SkImageEncoder.h"
 #include "SkMD5.h"
+#include "SkPngChunkReader.h"
 #include "SkRandom.h"
 #include "SkStream.h"
 #include "SkStreamPriv.h"
-#include "SkPngChunkReader.h"
 #include "Test.h"
 
 #include "png.h"
diff --git a/tests/ColorFilterTest.cpp b/tests/ColorFilterTest.cpp
index ad2dd0f..b6456a6 100644
--- a/tests/ColorFilterTest.cpp
+++ b/tests/ColorFilterTest.cpp
@@ -5,10 +5,12 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkColor.h"
 #include "SkColorFilter.h"
 #include "SkColorPriv.h"
 #include "SkLumaColorFilter.h"
+#include "SkRandom.h"
 #include "SkReadBuffer.h"
 #include "SkWriteBuffer.h"
 #include "SkRandom.h"
diff --git a/tests/ColorSpaceTest.cpp b/tests/ColorSpaceTest.cpp
index df253c0..65e0450 100644
--- a/tests/ColorSpaceTest.cpp
+++ b/tests/ColorSpaceTest.cpp
@@ -10,6 +10,7 @@
 #include "SkColorSpace.h"
 #include "SkColorSpace_Base.h"
 #include "SkColorSpace_XYZ.h"
+#include "SkColorSpacePriv.h"
 #include "Test.h"
 
 #include "png.h"
@@ -111,6 +112,17 @@
             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 =
@@ -135,6 +147,28 @@
         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 =
@@ -142,6 +176,26 @@
     REPORTER_ASSERT(r, strangeColorSpace != namedColorSpace);
 }
 
+DEF_TEST(ColorSpaceAdobeCompare, r) {
+    // Create an sRGB color space by name
+    sk_sp<SkColorSpace> namedColorSpace = SkColorSpace::MakeNamed(SkColorSpace::kAdobeRGB_Named);
+
+    // Create an sRGB color space by value
+    SkMatrix44 adobeToxyzD50(SkMatrix44::kUninitialized_Constructor);
+    adobeToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50);
+
+    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> rgbColorSpace = SkColorSpace::MakeRGB(fn, adobeToxyzD50);
+    REPORTER_ASSERT(r, rgbColorSpace == namedColorSpace);
+}
+
 DEF_TEST(ColorSpace_Named, r) {
     const struct {
         SkColorSpace::Named fNamed;
@@ -365,3 +419,23 @@
     sk_sp<SkColorSpace> cs = SkColorSpace::MakeICC(data->data(), data->size());
     REPORTER_ASSERT(r, !cs);
 }
+
+DEF_TEST(ColorSpace_MatrixHash, r) {
+    sk_sp<SkColorSpace> srgb = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
+
+    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;
+
+    SkMatrix44 srgbMat(SkMatrix44::kUninitialized_Constructor);
+    srgbMat.set3x3RowMajorf(gSRGB_toXYZD50);
+    sk_sp<SkColorSpace> strange = SkColorSpace::MakeRGB(fn, srgbMat);
+
+    REPORTER_ASSERT(r, *as_CSB(srgb)->toXYZD50() == *as_CSB(strange)->toXYZD50());
+    REPORTER_ASSERT(r, as_CSB(srgb)->toXYZD50Hash() == as_CSB(strange)->toXYZD50Hash());
+}
diff --git a/tests/DFPathRendererTest.cpp b/tests/DFPathRendererTest.cpp
index c499c08..7fb8c8a 100644
--- a/tests/DFPathRendererTest.cpp
+++ b/tests/DFPathRendererTest.cpp
@@ -42,19 +42,18 @@
     shape = shape.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, 1.f);
 
     GrPaint paint;
-    paint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+    paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
 
     GrNoClip noClip;
-    GrPathRenderer::DrawPathArgs args;
-    args.fPaint = &paint;
-    args.fUserStencilSettings = &GrUserStencilSettings::kUnused;
-    args.fRenderTargetContext = renderTargetContext;
-    args.fClip = &noClip;
-    args.fResourceProvider = rp;
-    args.fViewMatrix = &matrix;
-    args.fShape = &shape;
-    args.fAAType = GrAAType::kCoverage;
-    args.fGammaCorrect = false;
+    GrPathRenderer::DrawPathArgs args{rp,
+                                      std::move(paint),
+                                      &GrUserStencilSettings::kUnused,
+                                      renderTargetContext,
+                                      &noClip,
+                                      &matrix,
+                                      &shape,
+                                      GrAAType::kCoverage,
+                                      false};
     pr->drawPath(args);
 }
 
diff --git a/tests/FontHostTest.cpp b/tests/FontHostTest.cpp
index 55c5244..3d54c57 100644
--- a/tests/FontHostTest.cpp
+++ b/tests/FontHostTest.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "Resources.h"
+#include "SkAutoMalloc.h"
 #include "SkEndian.h"
 #include "SkFontStream.h"
 #include "SkOSFile.h"
diff --git a/tests/FrontBufferedStreamTest.cpp b/tests/FrontBufferedStreamTest.cpp
index a386274..69ff488 100644
--- a/tests/FrontBufferedStreamTest.cpp
+++ b/tests/FrontBufferedStreamTest.cpp
@@ -5,12 +5,12 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
 #include "SkFrontBufferedStream.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
-#include "SkTypes.h"
 #include "Test.h"
 
 static void test_read(skiatest::Reporter* reporter, SkStream* bufferedStream,
diff --git a/tests/GLProgramsTest.cpp b/tests/GLProgramsTest.cpp
index f981793..ce79d0e 100644
--- a/tests/GLProgramsTest.cpp
+++ b/tests/GLProgramsTest.cpp
@@ -167,9 +167,7 @@
 }
 
 static void set_random_xpf(GrPaint* paint, GrProcessorTestData* d) {
-    sk_sp<GrXPFactory> xpf(GrProcessorTestFactory<GrXPFactory>::Make(d));
-    SkASSERT(xpf);
-    paint->setXPFactory(std::move(xpf));
+    paint->setXPFactory(GrXPFactoryTestFactory::Get(d));
 }
 
 static sk_sp<GrFragmentProcessor> create_random_proc_tree(GrProcessorTestData* d,
@@ -327,7 +325,7 @@
 
         GrPaint grPaint;
 
-        sk_sp<GrDrawOp> op(GrRandomDrawOp(&random, context));
+        std::unique_ptr<GrDrawOp> op(GrRandomDrawOp(&random, context));
         SkASSERT(op);
 
         GrProcessorTestData ptd(&random, context, context->caps(),
@@ -341,8 +339,8 @@
         static constexpr GrAAType kAATypes[] = {GrAAType::kNone, GrAAType::kCoverage};
         GrAAType aaType = kAATypes[random.nextULessThan(SK_ARRAY_COUNT(kAATypes))];
 
-        renderTargetContext->priv().testingOnly_addDrawOp(grPaint, aaType, std::move(op), uss,
-                                                          snapToCenters);
+        renderTargetContext->priv().testingOnly_addDrawOp(std::move(grPaint), aaType, std::move(op),
+                                                          uss, snapToCenters);
     }
     // Flush everything, test passes if flush is successful(ie, no asserts are hit, no crashes)
     drawingManager->flush();
@@ -363,12 +361,12 @@
     for (int i = 0; i < fpFactoryCnt; ++i) {
         // Since FP factories internally randomize, call each 10 times.
         for (int j = 0; j < 10; ++j) {
-            sk_sp<GrDrawOp> op(GrRandomDrawOp(&random, context));
+            std::unique_ptr<GrDrawOp> op(GrRandomDrawOp(&random, context));
             SkASSERT(op);
             GrProcessorTestData ptd(&random, context, context->caps(),
                                     renderTargetContext.get(), dummyTextures);
             GrPaint grPaint;
-            grPaint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+            grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
 
             sk_sp<GrFragmentProcessor> fp(
                 GrProcessorTestFactory<GrFragmentProcessor>::MakeIdx(i, &ptd));
@@ -376,7 +374,7 @@
                 BlockInputFragmentProcessor::Make(std::move(fp)));
             grPaint.addColorFragmentProcessor(std::move(blockFP));
 
-            renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
+            renderTargetContext->priv().testingOnly_addDrawOp(std::move(grPaint), GrAAType::kNone,
                                                               std::move(op));
             drawingManager->flush();
         }
diff --git a/tests/GrPorterDuffTest.cpp b/tests/GrPorterDuffTest.cpp
index ce89793..3132fee 100644
--- a/tests/GrPorterDuffTest.cpp
+++ b/tests/GrPorterDuffTest.cpp
@@ -59,7 +59,6 @@
     kNone_OptFlags                    = GrXferProcessor::kNone_OptFlags,
     kSkipDraw_OptFlag                 = GrXferProcessor::kSkipDraw_OptFlag,
     kIgnoreColor_OptFlag              = GrXferProcessor::kIgnoreColor_OptFlag,
-    kIgnoreCoverage_OptFlag           = GrXferProcessor::kIgnoreCoverage_OptFlag,
     kCanTweakAlphaForCoverage_OptFlag = GrXferProcessor::kCanTweakAlphaForCoverage_OptFlag
 };
 
@@ -68,11 +67,12 @@
     struct XPInfo {
         XPInfo(skiatest::Reporter* reporter, SkBlendMode xfermode, const GrCaps& caps,
                const GrPipelineAnalysis& analysis) {
-            sk_sp<GrXPFactory> xpf(GrPorterDuffXPFactory::Make(xfermode));
+            const GrXPFactory* xpf = GrPorterDuffXPFactory::Get(xfermode);
             sk_sp<GrXferProcessor> xp(xpf->createXferProcessor(analysis, false, nullptr, caps));
             TEST_ASSERT(!xpf->willNeedDstTexture(caps, analysis));
             xpf->getInvariantBlendedColor(analysis.fColorPOI, &fBlendedColor);
-            fOptFlags = xp->getOptimizations(analysis, false, nullptr, caps);
+            GrColor ignoredOverrideColor;
+            fOptFlags = xp->getOptimizations(analysis, false, &ignoredOverrideColor, caps);
             GetXPOutputTypes(xp.get(), &fPrimaryOutputType, &fSecondaryOutputType);
             xp->getBlendInfo(&fBlendInfo);
             TEST_ASSERT(!xp->willReadDstColor());
@@ -136,7 +136,6 @@
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
                 TEST_ASSERT((kSkipDraw_OptFlag |
                              kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag |
                              kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
@@ -326,7 +325,6 @@
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
                 TEST_ASSERT((kSkipDraw_OptFlag |
                              kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag |
                              kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
@@ -496,8 +494,7 @@
                 TEST_ASSERT(!xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(0 == xpi.fBlendedColor.fKnownColor);
                 TEST_ASSERT(kRGBA_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kIgnoreColor_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -511,7 +508,7 @@
                 TEST_ASSERT(154 == GrColorUnpackB(xpi.fBlendedColor.fKnownColor));
                 TEST_ASSERT((kR_GrColorComponentFlag |
                              kB_GrColorComponentFlag) == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -524,7 +521,6 @@
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
                 TEST_ASSERT((kSkipDraw_OptFlag |
                              kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag |
                              kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
@@ -536,8 +532,7 @@
             case SkBlendMode::kSrcOver:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT((kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -548,8 +543,7 @@
             case SkBlendMode::kDstOver:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -560,7 +554,7 @@
             case SkBlendMode::kSrcIn:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -571,7 +565,7 @@
             case SkBlendMode::kDstIn:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -582,7 +576,7 @@
             case SkBlendMode::kSrcOut:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -593,8 +587,7 @@
             case SkBlendMode::kDstOut:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -605,8 +598,7 @@
             case SkBlendMode::kSrcATop:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -617,7 +609,7 @@
             case SkBlendMode::kDstATop:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -628,8 +620,7 @@
             case SkBlendMode::kXor:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -640,8 +631,7 @@
             case SkBlendMode::kPlus:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -652,7 +642,7 @@
             case SkBlendMode::kModulate:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -663,8 +653,7 @@
             case SkBlendMode::kScreen:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -724,7 +713,6 @@
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
                 TEST_ASSERT((kSkipDraw_OptFlag |
                              kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag |
                              kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
@@ -772,7 +760,6 @@
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
                 TEST_ASSERT((kSkipDraw_OptFlag |
                              kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag |
                              kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
@@ -899,8 +886,7 @@
                 TEST_ASSERT(!xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(0 == xpi.fBlendedColor.fKnownColor);
                 TEST_ASSERT(kRGBA_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kIgnoreColor_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -914,7 +900,7 @@
                 TEST_ASSERT(255 == GrColorUnpackA(xpi.fBlendedColor.fKnownColor));
                 TEST_ASSERT((kG_GrColorComponentFlag |
                              kA_GrColorComponentFlag) == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -927,7 +913,6 @@
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
                 TEST_ASSERT((kSkipDraw_OptFlag |
                              kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag |
                              kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
@@ -942,7 +927,7 @@
                 TEST_ASSERT(255 == GrColorUnpackA(xpi.fBlendedColor.fKnownColor));
                 TEST_ASSERT((kG_GrColorComponentFlag |
                              kA_GrColorComponentFlag) == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -953,8 +938,7 @@
             case SkBlendMode::kDstOver:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -965,7 +949,7 @@
             case SkBlendMode::kSrcIn:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -978,7 +962,6 @@
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
                 TEST_ASSERT((kSkipDraw_OptFlag |
                              kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag |
                              kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
@@ -990,7 +973,7 @@
             case SkBlendMode::kSrcOut:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -1002,8 +985,7 @@
                 TEST_ASSERT(!xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(0 == xpi.fBlendedColor.fKnownColor);
                 TEST_ASSERT(kRGBA_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreColor_OptFlag |
-                             kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kIgnoreColor_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kNone_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -1014,7 +996,7 @@
             case SkBlendMode::kSrcATop:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -1025,8 +1007,7 @@
             case SkBlendMode::kDstATop:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -1037,7 +1018,7 @@
             case SkBlendMode::kXor:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -1048,8 +1029,7 @@
             case SkBlendMode::kPlus:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -1060,7 +1040,7 @@
             case SkBlendMode::kModulate:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kNone_OptFlags == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -1071,8 +1051,7 @@
             case SkBlendMode::kScreen:
                 TEST_ASSERT(xpi.fBlendedColor.fWillBlendWithDst);
                 TEST_ASSERT(kNone_GrColorComponentFlags == xpi.fBlendedColor.fKnownColorFlags);
-                TEST_ASSERT((kIgnoreCoverage_OptFlag |
-                             kCanTweakAlphaForCoverage_OptFlag) == xpi.fOptFlags);
+                TEST_ASSERT(kCanTweakAlphaForCoverage_OptFlag == xpi.fOptFlags);
                 TEST_ASSERT(kModulate_OutputType == xpi.fPrimaryOutputType);
                 TEST_ASSERT(kNone_OutputType == xpi.fSecondaryOutputType);
                 TEST_ASSERT(kAdd_GrBlendEquation == xpi.fBlendInfo.fEquation);
@@ -1118,7 +1097,7 @@
     SkASSERT(kRGBA_GrColorComponentFlags == colorPOI.validFlags());
     SkASSERT(covPOI.isFourChannelOutput());
 
-    sk_sp<GrXPFactory> xpf(GrPorterDuffXPFactory::Make(SkBlendMode::kSrcOver));
+    const GrXPFactory* xpf = GrPorterDuffXPFactory::Get(SkBlendMode::kSrcOver);
     TEST_ASSERT(!xpf->willNeedDstTexture(caps, analysis));
 
     sk_sp<GrXferProcessor> xp(xpf->createXferProcessor(analysis, false, nullptr, caps));
@@ -1192,7 +1171,7 @@
             }
             for (int m = 0; m <= (int)SkBlendMode::kLastCoeffMode; m++) {
                 SkBlendMode xfermode = static_cast<SkBlendMode>(m);
-                sk_sp<GrXPFactory> xpf(GrPorterDuffXPFactory::Make(xfermode));
+                const GrXPFactory* xpf = GrPorterDuffXPFactory::Get(xfermode);
                 GrXferProcessor::DstTexture* dstTexture =
                         xpf->willNeedDstTexture(caps, analysis) ? &fakeDstTexture : 0;
                 sk_sp<GrXferProcessor> xp(
@@ -1202,7 +1181,8 @@
                     return;
                 }
                 TEST_ASSERT(!xp->hasSecondaryOutput());
-                xp->getOptimizations(analysis, false, 0, caps);
+                GrColor ignoredOverrideColor;
+                xp->getOptimizations(analysis, false, &ignoredOverrideColor, caps);
                 TEST_ASSERT(!xp->hasSecondaryOutput());
             }
         }
diff --git a/tests/HSVRoundTripTest.cpp b/tests/HSVRoundTripTest.cpp
new file mode 100644
index 0000000..4d25895
--- /dev/null
+++ b/tests/HSVRoundTripTest.cpp
@@ -0,0 +1,30 @@
+/*
+ * 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 "Test.h"
+
+#include "SkColor.h"
+
+DEF_TEST(ColorToHSVRoundTrip, reporter) {
+    SkScalar hsv[3];
+    for (U8CPU r = 0; r <= 255; r++) {
+        for (U8CPU g = 0; g <= 255; g++) {
+            for (U8CPU b = 0; b <= 255; b++) {
+                SkColor color = SkColorSetARGBInline(0xFF, r, g, b);
+                SkColorToHSV(color, hsv);
+                SkColor result = SkHSVToColor(0xFF, hsv);
+                if (result != color) {
+                    ERRORF(reporter, "HSV roundtrip mismatch!\n"
+                                     "\toriginal: %X\n"
+                                     "\tHSV: %f, %f, %f\n"
+                                     "\tresult: %X\n",
+                           color, hsv[0], hsv[1], hsv[2], result);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/ImageIsOpaqueTest.cpp b/tests/ImageIsOpaqueTest.cpp
index 21ddb5d..cbbe331 100644
--- a/tests/ImageIsOpaqueTest.cpp
+++ b/tests/ImageIsOpaqueTest.cpp
@@ -125,6 +125,7 @@
         GetResourceAsImage("mandrill_128.png"),
         GetResourceAsImage("color_wheel.jpg"),
         SkImage::MakeFromPicture(make_picture(), { 10, 10 }, nullptr, nullptr,
+                                 SkImage::BitDepth::kU8,
                                  SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named)),
     })
     {
diff --git a/tests/ImageStorageTest.cpp b/tests/ImageStorageTest.cpp
index 835aec5..8ffb82d 100644
--- a/tests/ImageStorageTest.cpp
+++ b/tests/ImageStorageTest.cpp
@@ -139,7 +139,7 @@
                 GrPaint paint;
                 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
                 paint.addColorFragmentProcessor(TestFP::Make(imageStorageTexture, mm, restrict));
-                rtContext->drawPaint(GrNoClip(), paint, SkMatrix::I());
+                rtContext->drawPaint(GrNoClip(), std::move(paint), SkMatrix::I());
                 std::unique_ptr<uint32_t[]> readData(new uint32_t[kS * kS]);
                 SkImageInfo info = SkImageInfo::Make(kS, kS, kRGBA_8888_SkColorType,
                                                      kPremul_SkAlphaType);
diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp
index c801d67..4f9d944 100644
--- a/tests/ImageTest.cpp
+++ b/tests/ImageTest.cpp
@@ -98,8 +98,9 @@
     draw_image_test_pattern(surface->getCanvas());
     return surface->makeImageSnapshot();
 }
-static sk_sp<SkImage> create_image_large() {
-    const SkImageInfo info = SkImageInfo::MakeN32(32000, 32, kOpaque_SkAlphaType);
+
+static sk_sp<SkImage> create_image_large(int maxTextureSize) {
+    const SkImageInfo info = SkImageInfo::MakeN32(maxTextureSize + 1, 32, kOpaque_SkAlphaType);
     auto surface(SkSurface::MakeRaster(info));
     surface->getCanvas()->clear(SK_ColorWHITE);
     SkPaint paint;
@@ -129,7 +130,7 @@
     SkCanvas* canvas = recorder.beginRecording(10, 10);
     canvas->clear(SK_ColorCYAN);
     return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(), SkISize::Make(10, 10),
-                                    nullptr, nullptr,
+                                    nullptr, nullptr, SkImage::BitDepth::kU8,
                                     SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
 };
 #endif
@@ -835,6 +836,9 @@
 
     testContext->makeCurrent();
     REPORTER_ASSERT(reporter, proxy);
+    auto createLarge = [context] {
+        return create_image_large(context->caps()->maxTextureSize());
+    };
     struct {
         std::function<sk_sp<SkImage> ()>                      fImageFactory;
         std::vector<SkImage::DeferredTextureImageUsageParams> fParams;
@@ -862,18 +866,18 @@
           }, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
         kNone_SkFilterQuality, 1, false },
         // Create an image that is too large to upload.
-        { create_image_large,    {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+        { createLarge, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
           kNone_SkFilterQuality, 1, false },
         // Create an image that is too large, but is scaled to an acceptable size.
-        { create_image_large, {{SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+        { createLarge, {{SkMatrix::I(), kMedium_SkFilterQuality, 4}},
           kMedium_SkFilterQuality, 16, true},
         // Create an image with multiple low filter qualities, make sure we round up.
-        { create_image_large, {{SkMatrix::I(), kNone_SkFilterQuality, 4},
-                               {SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+        { createLarge, {{SkMatrix::I(), kNone_SkFilterQuality, 4},
+                        {SkMatrix::I(), kMedium_SkFilterQuality, 4}},
           kMedium_SkFilterQuality, 16, true},
         // Create an image with multiple prescale levels, make sure we chose the minimum scale.
-        { create_image_large, {{SkMatrix::I(), kMedium_SkFilterQuality, 5},
-                               {SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+        { createLarge, {{SkMatrix::I(), kMedium_SkFilterQuality, 5},
+                        {SkMatrix::I(), kMedium_SkFilterQuality, 4}},
           kMedium_SkFilterQuality, 16, true},
     };
 
diff --git a/tests/IntTextureTest.cpp b/tests/IntTextureTest.cpp
index 5e3fde2..a3e17bf 100644
--- a/tests/IntTextureTest.cpp
+++ b/tests/IntTextureTest.cpp
@@ -15,14 +15,15 @@
 
 template <typename I>
 static SK_WHEN(std::is_integral<I>::value && 4 == sizeof(I), void)
-check_pixels(skiatest::Reporter* reporter, int w, int h,
-                         const I exepctedData[], const I actualData[]) {
+check_pixels(skiatest::Reporter* reporter, int w, int h, const I exepctedData[],
+             const I actualData[], const char* testName) {
     for (int j = 0; j < h; ++j) {
         for (int i = 0; i < w; ++i) {
             I expected = exepctedData[j * w + i];
             I actual = actualData[j * w + i];
             if (expected != actual) {
-                ERRORF(reporter, "Expected 0x08%x, got 0x%08x at %d, %d.", expected, actual, i, j);
+                ERRORF(reporter, "[%s] Expected 0x08%x, got 0x%08x at %d, %d.", testName, expected,
+                       actual, i, j);
                 return;
             }
         }
@@ -86,7 +87,7 @@
     success = texture->readPixels(0, 0, kS, kS, kRGBA_8888_sint_GrPixelConfig, readData.get());
     REPORTER_ASSERT(reporter, success);
     if (success) {
-        check_pixels(reporter, kS, kS, testData.get(), readData.get());
+        check_pixels(reporter, kS, kS, testData.get(), readData.get(), "readPixels");
     }
 
     // readPixels should fail if we attempt to use the unpremul flag with an integer texture.
@@ -114,7 +115,7 @@
                                           kRGBA_8888_sint_GrPixelConfig, readData.get());
         REPORTER_ASSERT(reporter, success);
         if (success) {
-            check_pixels(reporter, kS, kS, testData.get(), readData.get());
+            check_pixels(reporter, kS, kS, testData.get(), readData.get(), "copyIntegerToInteger");
         }
     }
 
@@ -176,7 +177,7 @@
         dst += rowBytes;
         src += rowBytes;
     }
-    check_pixels(reporter, kS, kS, overwrittenTestData.get(), readData.get());
+    check_pixels(reporter, kS, kS, overwrittenTestData.get(), readData.get(), "overwrite");
 
     // Test drawing from the integer texture to a fixed point texture. To avoid any premul issues
     // we init the int texture with 0s and 1s and make alpha always be 1. We expect that 1s turn
@@ -196,13 +197,20 @@
     sk_sp<GrRenderTargetContext> rtContext = context->makeRenderTargetContext(
             SkBackingFit::kExact, kS, kS, kRGBA_8888_GrPixelConfig, nullptr);
 
-    for (auto filter : {GrSamplerParams::kNone_FilterMode,
-                        GrSamplerParams::kBilerp_FilterMode,
-                        GrSamplerParams::kMipMap_FilterMode}) {
+    struct {
+        GrSamplerParams::FilterMode fMode;
+        const char* fName;
+    } kNamedFilters[] ={
+        { GrSamplerParams::kNone_FilterMode, "filter-none" },
+        { GrSamplerParams::kBilerp_FilterMode, "filter-bilerp" },
+        { GrSamplerParams::kMipMap_FilterMode, "filter-mipmap" }
+    };
+
+    for (auto filter : kNamedFilters) {
         SkMatrix m;
         m.setIDiv(kS, kS);
         sk_sp<GrFragmentProcessor> fp(GrSimpleTextureEffect::Make(texture.get(), nullptr, m,
-                                                                  filter));
+                                                                  filter.fMode));
         REPORTER_ASSERT(reporter, fp);
         if (!fp) {
             return;
@@ -211,11 +219,11 @@
         GrPaint paint;
         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
         paint.addColorFragmentProcessor(fp);
-        rtContext->drawPaint(GrNoClip(), paint, SkMatrix::I());
+        rtContext->drawPaint(GrNoClip(), std::move(paint), SkMatrix::I());
         SkImageInfo readInfo = SkImageInfo::Make(kS, kS, kRGBA_8888_SkColorType,
                                                  kPremul_SkAlphaType);
         rtContext->readPixels(readInfo, actualData.get(), 0, 0, 0);
-        check_pixels(reporter, kS, kS, expectedData.get(), actualData.get());
+        check_pixels(reporter, kS, kS, expectedData.get(), actualData.get(), filter.fName);
     }
 
     // No rendering to integer textures.
diff --git a/tests/MallocPixelRefTest.cpp b/tests/MallocPixelRefTest.cpp
index 09e1b93..7e2bece 100644
--- a/tests/MallocPixelRefTest.cpp
+++ b/tests/MallocPixelRefTest.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkData.h"
 #include "SkMallocPixelRef.h"
 #include "Test.h"
diff --git a/tests/PDFDeflateWStreamTest.cpp b/tests/PDFDeflateWStreamTest.cpp
index 11d2370..dcb7547 100644
--- a/tests/PDFDeflateWStreamTest.cpp
+++ b/tests/PDFDeflateWStreamTest.cpp
@@ -5,9 +5,12 @@
  * found in the LICENSE file.
  */
 
+#include "Test.h"
+
+#ifdef SK_SUPPORT_PDF
+
 #include "SkDeflate.h"
 #include "SkRandom.h"
-#include "Test.h"
 
 namespace {
 
@@ -162,3 +165,5 @@
     SkDeflateWStream emptyDeflateWStream(nullptr);
     REPORTER_ASSERT(r, !emptyDeflateWStream.writeText("FOO"));
 }
+
+#endif
diff --git a/tests/PDFGlyphsToUnicodeTest.cpp b/tests/PDFGlyphsToUnicodeTest.cpp
index 0d87cd7d..332520b 100644
--- a/tests/PDFGlyphsToUnicodeTest.cpp
+++ b/tests/PDFGlyphsToUnicodeTest.cpp
@@ -5,11 +5,14 @@
  * found in the LICENSE file.
  */
 
+#include "Test.h"
+
+#ifdef SK_SUPPORT_PDF
+
 #include "SkBitSet.h"
 #include "SkData.h"
 #include "SkPDFMakeToUnicodeCmap.h"
 #include "SkStream.h"
-#include "Test.h"
 
 static const int kMaximumGlyphCount = SK_MaxU16 + 1;
 
@@ -178,3 +181,5 @@
     REPORTER_ASSERT(reporter, stream_equals(buffer2, 0, expectedResult2,
                                             buffer2.bytesWritten()));
 }
+
+#endif
diff --git a/tests/PDFJpegEmbedTest.cpp b/tests/PDFJpegEmbedTest.cpp
index 17dbac8..100c9b3 100644
--- a/tests/PDFJpegEmbedTest.cpp
+++ b/tests/PDFJpegEmbedTest.cpp
@@ -9,7 +9,6 @@
 #include "SkData.h"
 #include "SkDocument.h"
 #include "SkImageGenerator.h"
-#include "SkJpegInfo.h"
 #include "SkStream.h"
 
 #include "Resources.h"
@@ -80,6 +79,10 @@
     REPORTER_ASSERT(r, !is_subset_of(cmykData.get(), pdfData.get()));
 }
 
+#ifdef SK_SUPPORT_PDF
+
+#include "SkJpegInfo.h"
+
 DEF_TEST(SkPDF_JpegIdentification, r) {
     static struct {
         const char* path;
@@ -208,3 +211,4 @@
         REPORTER_ASSERT(r, !SkIsJFIF(data.get(), &info));
     }
 }
+#endif
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 81b33ba..de9af07 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -5,6 +5,10 @@
  * found in the LICENSE file.
  */
 
+#include "Test.h"
+
+#ifdef SK_SUPPORT_PDF
+
 #include "Resources.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
@@ -24,7 +28,6 @@
 #include "SkSpecialImage.h"
 #include "SkStream.h"
 #include "SkTypes.h"
-#include "Test.h"
 #include "sk_tool_utils.h"
 
 #include <cstdlib>
@@ -486,3 +489,4 @@
         REPORTER_ASSERT(reporter, roundTrip == i);
     }
 }
+#endif
diff --git a/tests/PaintBreakTextTest.cpp b/tests/PaintBreakTextTest.cpp
index 474bbf6..b716c60 100644
--- a/tests/PaintBreakTextTest.cpp
+++ b/tests/PaintBreakTextTest.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkPaint.h"
 #include "Test.h"
 
diff --git a/tests/PaintTest.cpp b/tests/PaintTest.cpp
index 76bfb02..55a82a7 100644
--- a/tests/PaintTest.cpp
+++ b/tests/PaintTest.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
 #include "SkLayerDrawLooper.h"
diff --git a/tests/PathOpsExtendedTest.cpp b/tests/PathOpsExtendedTest.cpp
index 9b08ba9..d70deb3 100644
--- a/tests/PathOpsExtendedTest.cpp
+++ b/tests/PathOpsExtendedTest.cpp
@@ -370,60 +370,55 @@
 static int testNumber = 55;
 static const char* testName = "pathOpTest";
 
-static void writeTestName(const char* nameSuffix, SkMemoryWStream& outFile) {
-    outFile.writeText(testName);
-    outFile.writeDecAsText(testNumber);
+static void appendTestName(const char* nameSuffix, SkString& out) {
+    out.appendf("%s%d", testName, testNumber);
     ++testNumber;
     if (nameSuffix) {
-        outFile.writeText(nameSuffix);
+        out.append(nameSuffix);
     }
 }
 
-static void outputToStream(const char* pathStr, const char* pathPrefix, const char* nameSuffix,
-        const char* testFunction, bool twoPaths, SkMemoryWStream& outFile) {
+static void appendTest(const char* pathStr, const char* pathPrefix, const char* nameSuffix,
+                       const char* testFunction, bool twoPaths, SkString& out) {
 #if 0
-    outFile.writeText("\n<div id=\"");
-    writeTestName(nameSuffix, outFile);
-    outFile.writeText("\">\n");
+    out.append("\n<div id=\"");
+    appendTestName(nameSuffix, out);
+    out.append("\">\n");
     if (pathPrefix) {
-        outFile.writeText(pathPrefix);
+        out.append(pathPrefix);
     }
-    outFile.writeText(pathStr);
-    outFile.writeText("</div>\n\n");
+    out.append(pathStr);
+    out.append("</div>\n\n");
 
-    outFile.writeText(marker);
-    outFile.writeText("    ");
-    writeTestName(nameSuffix, outFile);
-    outFile.writeText(",\n\n\n");
+    out.append(marker);
+    out.append("    ");
+    appendTestName(nameSuffix, out);
+    out.append(",\n\n\n");
 #endif
-    outFile.writeText("static void ");
-    writeTestName(nameSuffix, outFile);
-    outFile.writeText("(skiatest::Reporter* reporter) {\n    SkPath path");
+    out.append("static void ");
+    appendTestName(nameSuffix, out);
+    out.append("(skiatest::Reporter* reporter) {\n    SkPath path");
     if (twoPaths) {
-        outFile.writeText(", pathB");
+        out.append(", pathB");
     }
-    outFile.writeText(";\n");
+    out.append(";\n");
     if (pathPrefix) {
-        outFile.writeText(pathPrefix);
+        out.append(pathPrefix);
     }
-    outFile.writeText(pathStr);
-    outFile.writeText("    ");
-    outFile.writeText(testFunction);
-    outFile.writeText("\n}\n\n");
+    out.appendf("%s    %s\n}\n\n", pathStr, testFunction);
 #if 0
-    outFile.writeText("static void (*firstTest)() = ");
-    writeTestName(nameSuffix, outFile);
-    outFile.writeText(";\n\n");
+    out.append("static void (*firstTest)() = ");
+    appendTestName(nameSuffix, out);
+    out.append(";\n\n");
 
-    outFile.writeText("static struct {\n");
-    outFile.writeText("    void (*fun)();\n");
-    outFile.writeText("    const char* str;\n");
-    outFile.writeText("} tests[] = {\n");
-    outFile.writeText("    TEST(");
-    writeTestName(nameSuffix, outFile);
-    outFile.writeText("),\n");
+    out.append("static struct {\n");
+    out.append("    void (*fun)();\n");
+    out.append("    const char* str;\n");
+    out.append("} tests[] = {\n");
+    out.append("    TEST(");
+    appendTestName(nameSuffix, out);
+    out.append("),\n");
 #endif
-    outFile.flush();
 }
 
 SK_DECLARE_STATIC_MUTEX(simplifyDebugOut);
@@ -444,9 +439,7 @@
     int result = comparePaths(state.fReporter, nullptr, path, out, *state.fBitmap);
     if (result) {
         SkAutoMutexAcquire autoM(simplifyDebugOut);
-        char temp[8192];
-        sk_bzero(temp, sizeof(temp));
-        SkMemoryWStream stream(temp, sizeof(temp));
+        SkString str;
         const char* pathPrefix = nullptr;
         const char* nameSuffix = nullptr;
         if (fillType == SkPath::kEvenOdd_FillType) {
@@ -454,8 +447,8 @@
             nameSuffix = "x";
         }
         const char testFunction[] = "testSimplify(reporter, path);";
-        outputToStream(pathStr, pathPrefix, nameSuffix, testFunction, false, stream);
-        SkDebugf("%s", temp);
+        appendTest(pathStr, pathPrefix, nameSuffix, testFunction, false, str);
+        SkDebugf("%s", str.c_str());
         REPORTER_ASSERT(state.fReporter, 0);
     }
     state.fReporter->bumpTestCount();
@@ -633,7 +626,7 @@
     }
 }
 
-void outputProgress(char* ramStr, const char* pathStr, SkPath::FillType pathFillType) {
+void PathOpsThreadState::outputProgress(const char* pathStr, SkPath::FillType pathFillType) {
     const char testFunction[] = "testSimplify(path);";
     const char* pathPrefix = nullptr;
     const char* nameSuffix = nullptr;
@@ -641,16 +634,14 @@
         pathPrefix = "    path.setFillType(SkPath::kEvenOdd_FillType);\n";
         nameSuffix = "x";
     }
-    SkMemoryWStream rRamStream(ramStr, PATH_STR_SIZE);
-    outputToStream(pathStr, pathPrefix, nameSuffix, testFunction, false, rRamStream);
+    appendTest(pathStr, pathPrefix, nameSuffix, testFunction, false, fPathStr);
 }
 
-void outputProgress(char* ramStr, const char* pathStr, SkPathOp op) {
+void PathOpsThreadState::outputProgress(const char* pathStr, SkPathOp op) {
     const char testFunction[] = "testOp(path);";
     SkASSERT((size_t) op < SK_ARRAY_COUNT(opSuffixes));
     const char* nameSuffix = opSuffixes[op];
-    SkMemoryWStream rRamStream(ramStr, PATH_STR_SIZE);
-    outputToStream(pathStr, nullptr, nameSuffix, testFunction, true, rRamStream);
+    appendTest(pathStr, nullptr, nameSuffix, testFunction, true, fPathStr);
 }
 
 void RunTestSet(skiatest::Reporter* reporter, TestDesc tests[], size_t count,
diff --git a/tests/PathOpsExtendedTest.h b/tests/PathOpsExtendedTest.h
index 35c14e78..9f8b0ae 100644
--- a/tests/PathOpsExtendedTest.h
+++ b/tests/PathOpsExtendedTest.h
@@ -49,8 +49,6 @@
                                        const char* filename);
 
 void initializeTests(skiatest::Reporter* reporter, const char* testName);
-void outputProgress(char* ramStr, const char* pathStr, SkPath::FillType );
-void outputProgress(char* ramStr, const char* pathStr, SkPathOp op);
 
 void RunTestSet(skiatest::Reporter* reporter, TestDesc tests[], size_t count,
                 void (*firstTest)(skiatest::Reporter* , const char* filename),
diff --git a/tests/PathOpsOpCircleThreadedTest.cpp b/tests/PathOpsOpCircleThreadedTest.cpp
index 40a02c1..7711dd0 100644
--- a/tests/PathOpsOpCircleThreadedTest.cpp
+++ b/tests/PathOpsOpCircleThreadedTest.cpp
@@ -45,7 +45,7 @@
                 pathStr.appendf("    testPathOp(reporter, path, pathB, %s, filename);\n",
                         SkPathOpsDebug::OpStr((SkPathOp) op));
                 pathStr.appendf("}\n");
-                outputProgress(state.fPathStr, pathStr.c_str(), (SkPathOp) op);
+                state.outputProgress(pathStr.c_str(), (SkPathOp) op);
             }
             if (!testPathOp(state.fReporter, pathA, pathB, (SkPathOp) op, "circles")) {
                 if (state.fReporter->verbose()) {
diff --git a/tests/PathOpsOpCubicThreadedTest.cpp b/tests/PathOpsOpCubicThreadedTest.cpp
index e76eb06..0bb8c19 100644
--- a/tests/PathOpsOpCubicThreadedTest.cpp
+++ b/tests/PathOpsOpCubicThreadedTest.cpp
@@ -56,7 +56,7 @@
                 pathStr.appendf("    testPathOp(reporter, path, pathB, %s, filename);\n",
                         SkPathOpsDebug::OpStr((SkPathOp) op));
                 pathStr.appendf("}\n");
-                outputProgress(state.fPathStr, pathStr.c_str(), (SkPathOp) op);
+                state.outputProgress(pathStr.c_str(), (SkPathOp) op);
             }
             if (!testPathOp(state.fReporter, pathA, pathB, (SkPathOp) op, "cubics")) {
                 if (state.fReporter->verbose()) {
diff --git a/tests/PathOpsOpLoopThreadedTest.cpp b/tests/PathOpsOpLoopThreadedTest.cpp
index 3854f90..04caac9 100644
--- a/tests/PathOpsOpLoopThreadedTest.cpp
+++ b/tests/PathOpsOpLoopThreadedTest.cpp
@@ -76,7 +76,7 @@
             pathStr.appendf("    testPathOp(reporter, path, pathB, kIntersect_SkPathOp,"
                     " filename);\n");
             pathStr.appendf("}\n");
-            outputProgress(state.fPathStr, pathStr.c_str(), kIntersect_SkPathOp);
+            state.outputProgress(pathStr.c_str(), kIntersect_SkPathOp);
         }
         testPathOp(state.fReporter, pathA, pathB, kIntersect_SkPathOp, "loops");
                 }
diff --git a/tests/PathOpsOpRectThreadedTest.cpp b/tests/PathOpsOpRectThreadedTest.cpp
index 5761572..e904a8d 100644
--- a/tests/PathOpsOpRectThreadedTest.cpp
+++ b/tests/PathOpsOpRectThreadedTest.cpp
@@ -64,7 +64,7 @@
                 pathStr.appendf("    testPathOp(reporter, path, pathB, %s, filename);\n",
                         SkPathOpsDebug::OpStr((SkPathOp) op));
                 pathStr.appendf("}\n\n");
-                outputProgress(state.fPathStr, pathStr.c_str(), (SkPathOp) op);
+                state.outputProgress(pathStr.c_str(), (SkPathOp) op);
             }
             if (!testPathOp(state.fReporter, pathA, pathB, (SkPathOp) op, "rects")) {
                 if (state.fReporter->verbose()) {
diff --git a/tests/PathOpsOpTest.cpp b/tests/PathOpsOpTest.cpp
index 2da0d4c..2f431c7 100644
--- a/tests/PathOpsOpTest.cpp
+++ b/tests/PathOpsOpTest.cpp
@@ -8374,7 +8374,84 @@
     testPathOp(reporter, path1, path2, (SkPathOp) 0, filename);

 }

 
+static void release_13(skiatest::Reporter* reporter, const char* filename) {
+    SkPath path;
+    path.setFillType((SkPath::FillType) 1);
+path.setFillType(SkPath::kEvenOdd_FillType);
+path.moveTo(SkBits2Float(0xd4438848), SkBits2Float(0xd488cf64));  // -3.35922e+12f, -4.70076e+12f
+path.lineTo(SkBits2Float(0xd43a056e), SkBits2Float(0xd4851696));  // -3.19582e+12f, -4.57288e+12f
+path.quadTo(SkBits2Float(0xd3d48e79), SkBits2Float(0xd49fb136), SkBits2Float(0x00000000), SkBits2Float(0xd4d4d4d4));  // -1.82585e+12f, -5.48698e+12f, 0, -7.31283e+12f
+path.quadTo(SkBits2Float(0xd3d06670), SkBits2Float(0xd4a0bb38), SkBits2Float(0xd41d628f), SkBits2Float(0xd472c531));  // -1.79014e+12f, -5.52269e+12f, -2.70385e+12f, -4.17076e+12f
+path.lineTo(SkBits2Float(0xd43a0559), SkBits2Float(0xd485168e));  // -3.19581e+12f, -4.57287e+12f
+path.lineTo(SkBits2Float(0xd446958b), SkBits2Float(0xd4810278));  // -3.41165e+12f, -4.43274e+12f
+path.lineTo(SkBits2Float(0xd443884a), SkBits2Float(0xd488cf65));  // -3.35922e+12f, -4.70076e+12f
+path.quadTo(SkBits2Float(0xd47efa09), SkBits2Float(0xd49fd72a), SkBits2Float(0xd4a63f0f), SkBits2Float(0xd4b83ab3));  // -4.38047e+12f, -5.49208e+12f, -5.71218e+12f, -6.33007e+12f
+path.lineTo(SkBits2Float(0xd497ca70), SkBits2Float(0xd4c4d4ae));  // -5.21549e+12f, -6.76305e+12f
+path.lineTo(SkBits2Float(0xd459d4d4), SkBits2Float(0xd4c4d4d4));  // -3.74231e+12f, -6.76307e+12f
+path.lineTo(SkBits2Float(0xd440daf9), SkBits2Float(0xd4c632d3));  // -3.31323e+12f, -6.81005e+12f
+path.lineTo(SkBits2Float(0xd4438848), SkBits2Float(0xd488cf64));  // -3.35922e+12f, -4.70076e+12f
+path.close();
+path.moveTo(SkBits2Float(0xd4767560), SkBits2Float(0xd4d1ca84));  // -4.23412e+12f, -7.20837e+12f
+path.lineTo(SkBits2Float(0xd4422174), SkBits2Float(0xd4d02069));  // -3.33514e+12f, -7.15118e+12f
+path.lineTo(SkBits2Float(0xd440daa3), SkBits2Float(0xd4c632d9));  // -3.31321e+12f, -6.81005e+12f
+path.lineTo(SkBits2Float(0xd41017bc), SkBits2Float(0xd4cb99b6));  // -2.47549e+12f, -6.99566e+12f
+path.lineTo(SkBits2Float(0xd442213b), SkBits2Float(0xd4d02067));  // -3.33512e+12f, -7.15117e+12f
+path.lineTo(SkBits2Float(0xd442d4d4), SkBits2Float(0xd4d4d4d4));  // -3.34718e+12f, -7.31283e+12f
+path.lineTo(SkBits2Float(0xd4767560), SkBits2Float(0xd4d1ca84));  // -4.23412e+12f, -7.20837e+12f
+path.close();
+path.moveTo(SkBits2Float(0xd46c7a11), SkBits2Float(0xd46c7a2e));  // -4.06264e+12f, -4.06265e+12f
+path.lineTo(SkBits2Float(0xd484e02c), SkBits2Float(0xd45fafcd));  // -4.56557e+12f, -3.84291e+12f
+path.lineTo(SkBits2Float(0xd462c867), SkBits2Float(0xd45655f7));  // -3.8961e+12f, -3.68226e+12f
+path.lineTo(SkBits2Float(0xd45ac463), SkBits2Float(0xd45ac505));  // -3.75839e+12f, -3.75843e+12f
+path.lineTo(SkBits2Float(0xd43d2fa9), SkBits2Float(0xd43d2fb5));  // -3.25019e+12f, -3.2502e+12f
+path.lineTo(SkBits2Float(0xd41d6287), SkBits2Float(0xd472c52a));  // -2.70385e+12f, -4.17076e+12f
+path.quadTo(SkBits2Float(0x00000000), SkBits2Float(0xd3db1b95), SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, -1.88212e+12f, 0, 0
+path.quadTo(SkBits2Float(0xd4b7efac), SkBits2Float(0x00000000), SkBits2Float(0xd4d0e88f), SkBits2Float(0xd40b8b46));  // -6.32e+12f, 0, -7.17804e+12f, -2.39735e+12f
+path.lineTo(SkBits2Float(0xd4d4d4d4), SkBits2Float(0x00000000));  // -7.31283e+12f, 0
+path.lineTo(SkBits2Float(0xdcdc154b), SkBits2Float(0x00000000));  // -4.95583e+17f, 0
+path.lineTo(SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4c4d477));  // -7.31283e+12f, -6.76303e+12f
+path.lineTo(SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d442));  // -7.31283e+12f, -7.31275e+12f
+path.lineTo(SkBits2Float(0xd4d4a691), SkBits2Float(0xd4d4d442));  // -7.30662e+12f, -7.31275e+12f
+path.lineTo(SkBits2Float(0xd454d4d4), SkBits2Float(0xd4d4aa30));  // -3.65641e+12f, -7.30711e+12f
+path.lineTo(SkBits2Float(0xd4bd9def), SkBits2Float(0xd4d43df0));  // -6.51519e+12f, -7.29258e+12f
+path.lineTo(SkBits2Float(0xd4767560), SkBits2Float(0xd4d1ca84));  // -4.23412e+12f, -7.20837e+12f
+path.lineTo(SkBits2Float(0xd497ca70), SkBits2Float(0xd4c4d4ae));  // -5.21549e+12f, -6.76305e+12f
+path.lineTo(SkBits2Float(0xd4bab953), SkBits2Float(0xd4c4d48e));  // -6.41579e+12f, -6.76304e+12f
+path.lineTo(SkBits2Float(0xd4a63f0f), SkBits2Float(0xd4b83ab3));  // -5.71218e+12f, -6.33007e+12f
+path.lineTo(SkBits2Float(0xd4ae61eb), SkBits2Float(0xd4ae61f4));  // -5.99174e+12f, -5.99174e+12f
+path.lineTo(SkBits2Float(0xd46c7a11), SkBits2Float(0xd46c7a2e));  // -4.06264e+12f, -4.06265e+12f
+path.close();
+path.moveTo(SkBits2Float(0xd46c7a11), SkBits2Float(0xd46c7a2e));  // -4.06264e+12f, -4.06265e+12f
+path.lineTo(SkBits2Float(0xd446965c), SkBits2Float(0xd4810237));  // -3.4117e+12f, -4.4327e+12f
+path.lineTo(SkBits2Float(0xd45ac549), SkBits2Float(0xd45ac55f));  // -3.75845e+12f, -3.75846e+12f
+path.lineTo(SkBits2Float(0xd46c7a11), SkBits2Float(0xd46c7a2e));  // -4.06264e+12f, -4.06265e+12f
+path.close();
+path.moveTo(SkBits2Float(0xd4b46028), SkBits2Float(0xd41e572a));  // -6.19766e+12f, -2.72027e+12f
+path.lineTo(SkBits2Float(0xd4cde20a), SkBits2Float(0xd434bb57));  // -7.07408e+12f, -3.10495e+12f
+path.lineTo(SkBits2Float(0xd4c75ffe), SkBits2Float(0xd46f215d));  // -6.85047e+12f, -4.10823e+12f
+path.lineTo(SkBits2Float(0xd4b46028), SkBits2Float(0xd41e572a));  // -6.19766e+12f, -2.72027e+12f
+path.close();
+
+    SkPath path1(path);
+    path.reset();
+    path.setFillType((SkPath::FillType) 0);
+path.setFillType(SkPath::kWinding_FillType);
+path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000));  // 0, 0
+path.quadTo(SkBits2Float(0x00000000), SkBits2Float(0xa5a50000), SkBits2Float(0xd4d4a5a5), SkBits2Float(0xd4d4d4d4));  // 0, -2.86229e-16f, -7.3065e+12f, -7.31283e+12f
+path.quadTo(SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4cfd4d4), SkBits2Float(0xd4d41dd4));  // -7.31283e+12f, -7.31283e+12f, -7.14103e+12f, -7.28827e+12f
+path.quadTo(SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d432d4), SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4a5a5d4));  // -7.31283e+12f, -7.29109e+12f, -7.31283e+12f, -5.69161e+12f
+path.quadTo(SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d4d4), SkBits2Float(0x00000000));  // -7.31283e+12f, -7.31283e+12f, -7.31283e+12f, 0
+path.moveTo(SkBits2Float(0xa5a5a500), SkBits2Float(0xd4d4d4a5));  // -2.87347e-16f, -7.31281e+12f
+path.quadTo(SkBits2Float(0xd4d4d4d4), SkBits2Float(0x2ad4d4d4), SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4cfd4d4));  // -7.31283e+12f, 3.78064e-13f, -7.31283e+12f, -7.14103e+12f
+path.quadTo(SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d4d4));  // -7.31283e+12f, -7.31283e+12f, -7.31283e+12f, -7.31283e+12f
+path.quadTo(SkBits2Float(0xd4d40000), SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d4d4), SkBits2Float(0xd4d4d4d4));  // -7.28426e+12f, -7.31283e+12f, -7.31283e+12f, -7.31283e+12f
+
+    SkPath path2(path);
+    testPathOpFuzz(reporter, path1, path2, (SkPathOp) 2, filename);
+}
+
 static struct TestDesc failTests[] = {
+    TEST(release_13),
     TEST(fuzzhang_1),
     TEST(fuzz763_57),
     TEST(fuzz763_56),
diff --git a/tests/PathOpsSimplifyDegenerateThreadedTest.cpp b/tests/PathOpsSimplifyDegenerateThreadedTest.cpp
index 71933df..3f49718 100644
--- a/tests/PathOpsSimplifyDegenerateThreadedTest.cpp
+++ b/tests/PathOpsSimplifyDegenerateThreadedTest.cpp
@@ -50,12 +50,12 @@
                     pathStr.appendf("    path.lineTo(%d, %d);\n", ex, ey);
                     pathStr.appendf("    path.lineTo(%d, %d);\n", fx, fy);
                     pathStr.appendf("    path.close();\n");
-                    outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kWinding_FillType);
+                    state.outputProgress(pathStr.c_str(), SkPath::kWinding_FillType);
                 }
                 testSimplify(path, false, out, state, pathStr.c_str());
                 path.setFillType(SkPath::kEvenOdd_FillType);
                 if (state.fReporter->verbose()) {
-                    outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kEvenOdd_FillType);
+                    state.outputProgress(pathStr.c_str(), SkPath::kEvenOdd_FillType);
                 }
                 testSimplify(path, true, out, state, pathStr.c_str());
             }
diff --git a/tests/PathOpsSimplifyQuadThreadedTest.cpp b/tests/PathOpsSimplifyQuadThreadedTest.cpp
index bca8d5a..55dc07b 100644
--- a/tests/PathOpsSimplifyQuadThreadedTest.cpp
+++ b/tests/PathOpsSimplifyQuadThreadedTest.cpp
@@ -61,12 +61,12 @@
                         pathStr.appendf("    path.close();\n");
                         pathStr.appendf("    testSimplify(reporter, path, filename);\n");
                         pathStr.appendf("}\n");
-                        outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kWinding_FillType);
+                        state.outputProgress(pathStr.c_str(), SkPath::kWinding_FillType);
                     }
                     testSimplify(path, false, out, state, pathStr.c_str());
                     path.setFillType(SkPath::kEvenOdd_FillType);
                     if (state.fReporter->verbose()) {
-                        outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kEvenOdd_FillType);
+                        state.outputProgress(pathStr.c_str(), SkPath::kEvenOdd_FillType);
                     }
                     testSimplify(path, true, out, state, pathStr.c_str());
                 }
diff --git a/tests/PathOpsSimplifyQuadralateralsThreadedTest.cpp b/tests/PathOpsSimplifyQuadralateralsThreadedTest.cpp
index 1ea55e2..6133042 100644
--- a/tests/PathOpsSimplifyQuadralateralsThreadedTest.cpp
+++ b/tests/PathOpsSimplifyQuadralateralsThreadedTest.cpp
@@ -63,12 +63,12 @@
                         pathStr.appendf("    path.close();\n");
                         pathStr.appendf("    testPathSimplify(reporter, path, filename);\n");
                         pathStr.appendf("}\n");
-                        outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kWinding_FillType);
+                        state.outputProgress(pathStr.c_str(), SkPath::kWinding_FillType);
                     }
                     testSimplify(path, false, out, state, pathStr.c_str());
                     path.setFillType(SkPath::kEvenOdd_FillType);
                     if (state.fReporter->verbose()) {
-                        outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kEvenOdd_FillType);
+                        state.outputProgress(pathStr.c_str(), SkPath::kEvenOdd_FillType);
                     }
                     testSimplify(path, true, out, state, pathStr.c_str());
                 }
diff --git a/tests/PathOpsSimplifyRectThreadedTest.cpp b/tests/PathOpsSimplifyRectThreadedTest.cpp
index 4ddc6f7..384d1b0 100644
--- a/tests/PathOpsSimplifyRectThreadedTest.cpp
+++ b/tests/PathOpsSimplifyRectThreadedTest.cpp
@@ -166,11 +166,11 @@
         }
         path.close();
         if (state.fReporter->verbose()) {
-            outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kWinding_FillType);
+            state.outputProgress(pathStr.c_str(), SkPath::kWinding_FillType);
         }
         testSimplify(path, false, out, state, pathStr.c_str());
         if (state.fReporter->verbose()) {
-            outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kEvenOdd_FillType);
+            state.outputProgress(pathStr.c_str(), SkPath::kEvenOdd_FillType);
         }
         testSimplify(path, true, out, state, pathStr.c_str());
     }
diff --git a/tests/PathOpsSimplifyTrianglesThreadedTest.cpp b/tests/PathOpsSimplifyTrianglesThreadedTest.cpp
index 671616c..372b667 100644
--- a/tests/PathOpsSimplifyTrianglesThreadedTest.cpp
+++ b/tests/PathOpsSimplifyTrianglesThreadedTest.cpp
@@ -53,13 +53,13 @@
                     pathStr.appendf("    path.lineTo(%d, %d);\n", ex, ey);
                     pathStr.appendf("    path.lineTo(%d, %d);\n", fx, fy);
                     pathStr.appendf("    path.close();\n");
-                    outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kWinding_FillType);
+                    state.outputProgress(pathStr.c_str(), SkPath::kWinding_FillType);
                 }
                 ShowTestName(&state, d, e, f, 0);
                 testSimplify(path, false, out, state, pathStr.c_str());
                 path.setFillType(SkPath::kEvenOdd_FillType);
                 if (state.fReporter->verbose()) {
-                    outputProgress(state.fPathStr, pathStr.c_str(), SkPath::kEvenOdd_FillType);
+                    state.outputProgress(pathStr.c_str(), SkPath::kEvenOdd_FillType);
                 }
                 ShowTestName(&state, d, e, f, 1);
                 testSimplify(path, true, out, state, pathStr.c_str());
diff --git a/tests/PathOpsSkpClipTest.cpp b/tests/PathOpsSkpClipTest.cpp
deleted file mode 100644
index 0c866bd..0000000
--- a/tests/PathOpsSkpClipTest.cpp
+++ /dev/null
@@ -1,1088 +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 "CrashHandler.h"
-// #include "OverwriteLine.h"
-#include "Resources.h"
-#include "SkBitmap.h"
-#include "SkCanvas.h"
-#include "SkColor.h"
-#include "SkColorPriv.h"
-#include "SkCommandLineFlags.h"
-#include "SkGraphics.h"
-#include "SkImageEncoder.h"
-#include "SkOSFile.h"
-#include "SkPathOpsDebug.h"
-#include "SkPicture.h"
-#include "SkTSort.h"
-#include "SkStream.h"
-#include "SkString.h"
-#include "SkTArray.h"
-#include "SkTDArray.h"
-#include "SkTaskGroup.h"
-#include "SkTemplates.h"
-#include "SkTSearch.h"
-#include "SkTime.h"
-
-#include <stdlib.h>
-
-/* add local exceptions here */
-/* TODO : add command flag interface */
-const struct SkipOverTest {
-    int directory;
-    const char* filename;
-    bool blamePathOps;
-} skipOver[] = {
-    { 2, "http___www_groupon_sg_.skp", false},  // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
-    { 6, "http___www_googleventures_com_.skp", true},  // addTCoincident SkASSERT(test->fT < 1);
-    { 7, "http___www_foxsports_nl_.skp", true},  // (no repro on mac) addT SkASSERT(this != other || fVerb == SkPath::kCubic_Verb)
-    {13, "http___www_modernqigong_com_.skp", false},  // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
-    {14, "http___www_devbridge_com_.skp", true},  // checkSmallCoincidence SkASSERT(!next->fSmall || checkMultiple);
-    {16, "http___www_1023world_net_.skp", false},  // bitmap decode assert (corrupt skp?)
-    {19, "http___www_alamdi_com_.skp", true},  // cubic/quad intersection
-    {26, "http___www_liveencounters_net_.skp", true},  // (no repro on mac) checkSmall addT:549 (line, expects cubic)
-    {28, "http___www_encros_fr_.skp", false},  // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
-    {37, "http___www_familysurvivalprotocol_wordpress_com_.skp", true},  // bumpSpan SkASSERT(span->fOppValue >= 0);
-    {39, "http___sufeinet_com_.skp", false}, // bitmap decode assert (corrupt skp?)
-    {41, "http___www_rano360_com_.skp", true}, // checkSmallCoincidence SkASSERT(!next->fSmall || checkMultiple);
-    {44, "http___www_firstunitedbank_com_.skp", true},  // addTCancel SkASSERT(oIndex > 0);
-    {46, "http___www_shinydemos_com_.skp", true},  // addSimpleAngle SkASSERT(index == count() - 2);
-    {48, "http___www_familysurvivalprotocol_com_.skp", true},  // bumpSpan SkASSERT "span->fOppValue >= 0"
-    {57, "http___www_lptemp_com_.skp", true}, // addTCoincident oPeek = &other->fTs[++oPeekIndex];
-    {71, "http___www_1milyonkahraman_org_.skp", true},  // addTCoincident SkASSERT(test->fT < 1);
-    {88, "http___www_apuntesdelechuza_wordpress_com_.skp", true},  // bumpSpan SkASSERT "span->fOppValue >= 0"
-    {89, "http___www_mobilizedconsulting_com_.skp", true}, // addTCancel SkASSERT(oIndex > 0);
-    {93, "http___www_simple_living_in_suffolk_co_uk_.skp", true},  // bumpSpan SkASSERT "span->fOppValue >= 0"
-};
-
-size_t skipOverCount = sizeof(skipOver) / sizeof(skipOver[0]);
-
-
-/* customize file in/out here */
-/* TODO : add command flag interface */
-#define CHROME_VERSION "1e5dfa4-4a995df"
-#define SUMMARY_RUN 1
-
-#ifdef SK_BUILD_FOR_WIN
-    #define DRIVE_SPEC "D:"
-    #define PATH_SLASH "\\"
-#else
-    #define DRIVE_SPEC ""
-    #define PATH_SLASH "/"
-#endif
-
-#define IN_DIR_PRE  DRIVE_SPEC PATH_SLASH "skps"   PATH_SLASH "slave"
-#define OUT_DIR_PRE DRIVE_SPEC PATH_SLASH "skpOut" PATH_SLASH "slave"
-#define OUT_DIR_SUM DRIVE_SPEC PATH_SLASH "skpOut" PATH_SLASH "summary"
-#define DIR_POST               PATH_SLASH "All"    PATH_SLASH CHROME_VERSION
-
-static const char outOpDir[]     = "opClip";
-static const char outOldDir[]    = "oldClip";
-static const char outStatusDir[] = "statusTest";
-
-static SkString get_in_path(int dirNo, const char* filename) {
-    SkString path;
-    SkASSERT(dirNo);
-    path.appendf("%s%d%s", IN_DIR_PRE, dirNo, DIR_POST);
-    if (!sk_exists(path.c_str())) {
-        SkDebugf("could not read %s\n", path.c_str());
-        return SkString();
-    }
-    if (filename) {
-        path.appendf("%s%s", PATH_SLASH, filename);
-        if (!sk_exists(path.c_str())) {
-            SkDebugf("could not read %s\n", path.c_str());
-            return SkString();
-        }
-    }
-    return path;
-}
-
-static void make_recursive_dir(const SkString& path) {
-    if (sk_exists(path.c_str())) {
-        return;
-    }
-    const char* pathStr = path.c_str();
-    int last = (int) path.size();
-    do {
-        while (last > 0 && pathStr[--last] != PATH_SLASH[0])
-            ;
-        SkASSERT(last > 0);
-        SkString shorter(pathStr, last);
-        if (sk_mkdir(shorter.c_str())) {
-            break;
-        }
-    } while (true);
-    do {
-        while (last < (int) path.size() && pathStr[++last] != PATH_SLASH[0])
-            ;
-        SkString shorter(pathStr, last);
-        SkAssertResult(sk_mkdir(shorter.c_str()));
-    } while (last < (int) path.size());
-}
-
-static SkString get_out_path(int dirNo, const char* dirName) {
-    SkString path;
-    SkASSERT(dirNo);
-    SkASSERT(dirName);
-    path.appendf("%s%d%s%s%s", OUT_DIR_PRE, dirNo, DIR_POST, PATH_SLASH, dirName);
-    make_recursive_dir(path);
-    return path;
-}
-
-static SkString get_sum_path(const char* dirName) {
-    SkString path;
-    SkASSERT(dirName);
-    path.appendf("%s%d%s%s", OUT_DIR_SUM, SUMMARY_RUN, PATH_SLASH, dirName);
-    SkDebugf("%s\n", path.c_str());
-    make_recursive_dir(path);
-    return path;
-}
-
-static SkString make_png_name(const char* filename) {
-    SkString pngName = SkString(filename);
-    pngName.remove(pngName.size() - 3, 3);
-    pngName.append("png");
-    return pngName;
-}
-
-////////////////////////////////////////////////////////
-
-enum TestStep {
-    kCompareBits,
-    kEncodeFiles,
-};
-
-enum {
-    kMaxLength = 256,
-    kMaxFiles = 128,
-    kSmallLimit = 1000,
-};
-
-struct TestResult {
-    void init(int dirNo) {
-        fDirNo = dirNo;
-        sk_bzero(fFilename, sizeof(fFilename));
-        fTestStep = kCompareBits;
-        fScale = 1;
-    }
-
-    void init(int dirNo, const SkString& filename) {
-        fDirNo = dirNo;
-        strcpy(fFilename, filename.c_str());
-        fTestStep = kCompareBits;
-        fScale = 1;
-    }
-
-    SkString status() {
-        SkString outStr;
-        outStr.printf("%s %d %d\n", fFilename, fPixelError, fTime);
-        return outStr;
-    }
-
-    SkString progress() {
-        SkString outStr;
-        outStr.printf("dir=%d %s ", fDirNo, fFilename);
-        if (fPixelError) {
-            outStr.appendf(" err=%d", fPixelError);
-        }
-        if (fTime) {
-            outStr.appendf(" time=%d", fTime);
-        }
-        if (fScale != 1) {
-            outStr.appendf(" scale=%d", fScale);
-        }
-        outStr.appendf("\n");
-        return outStr;
-
-    }
-
-    void test(int dirNo, const SkString& filename) {
-        init(dirNo);
-        strcpy(fFilename, filename.c_str());
-        testOne();
-    }
-
-    void testOne();
-
-    char fFilename[kMaxLength];
-    TestStep fTestStep;
-    int fDirNo;
-    int fPixelError;
-    SkMSec fTime;
-    int fScale;
-};
-
-class SortByPixel : public TestResult {
-public:
-    bool operator<(const SortByPixel& rh) const {
-        return fPixelError < rh.fPixelError;
-    }
-};
-
-class SortByTime : public TestResult {
-public:
-    bool operator<(const SortByTime& rh) const {
-        return fTime < rh.fTime;
-    }
-};
-
-class SortByName : public TestResult {
-public:
-    bool operator<(const SortByName& rh) const {
-        return strcmp(fFilename, rh.fFilename) < 0;
-    }
-};
-
-struct TestState {
-    void init(int dirNo) {
-        fResult.init(dirNo);
-    }
-
-    SkTDArray<SortByPixel> fPixelWorst;
-    SkTDArray<SortByTime> fSlowest;
-    TestResult fResult;
-};
-
-struct TestRunner {
-    ~TestRunner();
-    void render();
-    SkTDArray<class TestRunnable*> fRunnables;
-};
-
-class TestRunnable {
-public:
-    void operator()() {
-        SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
-        (*fTestFun)(&fState);
-    }
-
-    TestState fState;
-    void (*fTestFun)(TestState*);
-};
-
-
-class TestRunnableDir : public TestRunnable {
-public:
-    TestRunnableDir(void (*testFun)(TestState*), int dirNo, TestRunner* runner) {
-        fState.init(dirNo);
-        fTestFun = testFun;
-    }
-
-};
-
-class TestRunnableFile : public TestRunnable {
-public:
-    TestRunnableFile(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner) {
-        fState.init(dirNo);
-        strcpy(fState.fResult.fFilename, name);
-        fTestFun = testFun;
-    }
-};
-
-class TestRunnableEncode : public TestRunnableFile {
-public:
-    TestRunnableEncode(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner)
-        : TestRunnableFile(testFun, dirNo, name, runner) {
-        fState.fResult.fTestStep = kEncodeFiles;
-    }
-};
-
-TestRunner::~TestRunner() {
-    for (int index = 0; index < fRunnables.count(); index++) {
-        delete fRunnables[index];
-    }
-}
-
-void TestRunner::render() {
-    SkTaskGroup().batch(fRunnables.count(), [&](int i) {
-        (*fRunnables[i])();
-    });
-}
-
-////////////////////////////////////////////////
-
-
-static int similarBits(const SkBitmap& gr, const SkBitmap& sk) {
-    const int kRowCount = 3;
-    const int kThreshold = 3;
-    int width = SkTMin(gr.width(), sk.width());
-    if (width < kRowCount) {
-        return true;
-    }
-    int height = SkTMin(gr.height(), sk.height());
-    if (height < kRowCount) {
-        return true;
-    }
-    int errorTotal = 0;
-    SkTArray<int, true> errorRows;
-    errorRows.push_back_n(width * kRowCount);
-    SkAutoLockPixels autoGr(gr);
-    SkAutoLockPixels autoSk(sk);
-    for (int y = 0; y < height; ++y) {
-        SkPMColor* grRow = gr.getAddr32(0, y);
-        SkPMColor* skRow = sk.getAddr32(0, y);
-        int* base = &errorRows[0];
-        int* cOut = &errorRows[y % kRowCount];
-        for (int x = 0; x < width; ++x) {
-            SkPMColor grColor = grRow[x];
-            SkPMColor skColor = skRow[x];
-            int dr = SkGetPackedR32(grColor) - SkGetPackedR32(skColor);
-            int dg = SkGetPackedG32(grColor) - SkGetPackedG32(skColor);
-            int db = SkGetPackedB32(grColor) - SkGetPackedB32(skColor);
-            int error = cOut[x] = SkTMax(SkAbs32(dr), SkTMax(SkAbs32(dg), SkAbs32(db)));
-            if (error < kThreshold || x < 2) {
-                continue;
-            }
-            if (base[x - 2] < kThreshold
-                    || base[width + x - 2] < kThreshold
-                    || base[width * 2 + x - 2] < kThreshold
-                    || base[x - 1] < kThreshold
-                    || base[width + x - 1] < kThreshold
-                    || base[width * 2 + x - 1] < kThreshold
-                    || base[x] < kThreshold
-                    || base[width + x] < kThreshold
-                    || base[width * 2 + x] < kThreshold) {
-                continue;
-            }
-            errorTotal += error;
-        }
-    }
-    return errorTotal;
-}
-
-static bool addError(TestState* data, const TestResult& testResult) {
-    if (testResult.fPixelError <= 0 && testResult.fTime <= 0) {
-        return false;
-    }
-    int worstCount = data->fPixelWorst.count();
-    int pixelError = testResult.fPixelError;
-    if (pixelError > 0) {
-        for (int index = 0; index < worstCount; ++index) {
-            if (pixelError > data->fPixelWorst[index].fPixelError) {
-                data->fPixelWorst[index] = *(SortByPixel*) &testResult;
-                return true;
-            }
-        }
-    }
-    int slowCount = data->fSlowest.count();
-    SkMSec time = testResult.fTime;
-    if (time > 0) {
-        for (int index = 0; index < slowCount; ++index) {
-            if (time > data->fSlowest[index].fTime) {
-                data->fSlowest[index] = *(SortByTime*) &testResult;
-                return true;
-            }
-        }
-    }
-    if (pixelError > 0 && worstCount < kMaxFiles) {
-        *data->fPixelWorst.append() = *(SortByPixel*) &testResult;
-        return true;
-    }
-    if (time > 0 && slowCount < kMaxFiles) {
-        *data->fSlowest.append() = *(SortByTime*) &testResult;
-        return true;
-    }
-    return false;
-}
-
-static SkMSec timePict(SkPicture* pic, SkCanvas* canvas) {
-    canvas->save();
-    SkScalar pWidth = pic->cullRect().width();
-    SkScalar pHeight = pic->cullRect().height();
-    const SkScalar maxDimension = 1000.0f;
-    const int slices = 3;
-    SkScalar xInterval = SkTMax(pWidth - maxDimension, 0.0f) / (slices - 1);
-    SkScalar yInterval = SkTMax(pHeight - maxDimension, 0.0f) / (slices - 1);
-    SkRect rect = {0, 0, SkTMin(maxDimension, pWidth), SkTMin(maxDimension, pHeight) };
-    canvas->clipRect(rect);
-    double start = SkTime::GetMSecs();
-    for (int x = 0; x < slices; ++x) {
-        for (int y = 0; y < slices; ++y) {
-            pic->playback(canvas);
-            canvas->translate(0, yInterval);
-        }
-        canvas->translate(xInterval, -yInterval * slices);
-    }
-    double end = SkTime::GetMSecs();
-    canvas->restore();
-    return static_cast<SkMSec>(end - start);
-}
-
-static void drawPict(SkPicture* pic, SkCanvas* canvas, int scale) {
-    canvas->clear(SK_ColorWHITE);
-    if (scale != 1) {
-        canvas->save();
-        canvas->scale(1.0f / scale, 1.0f / scale);
-    }
-    pic->playback(canvas);
-    if (scale != 1) {
-        canvas->restore();
-    }
-}
-
-static void writePict(const SkBitmap& bitmap, const char* outDir, const char* pngName) {
-    SkString outFile = get_sum_path(outDir);
-    outFile.appendf("%s%s", PATH_SLASH, pngName);
-    if (!sk_tool_utils::EncodeImageToFile(outFile.c_str(), bitmap, SkEncodedImageFormat::kPNG, 100)) {
-        SkDebugf("unable to encode gr %s (width=%d height=%d)\n", pngName,
-                    bitmap.width(), bitmap.height());
-    }
-}
-
-void TestResult::testOne() {
-    sk_sp<SkPicture> pic;
-    {
-    #if DEBUG_SHOW_TEST_NAME
-        if (fTestStep == kCompareBits) {
-            SkString testName(fFilename);
-            const char http[] = "http";
-            if (testName.startsWith(http)) {
-                testName.remove(0, sizeof(http) - 1);
-            }
-            while (testName.startsWith("_")) {
-                testName.remove(0, 1);
-            }
-            const char dotSkp[] = ".skp";
-            if (testName.endsWith(dotSkp)) {
-                size_t len = testName.size();
-                testName.remove(len - (sizeof(dotSkp) - 1), sizeof(dotSkp) - 1);
-            }
-            testName.prepend("skp");
-            testName.append("1");
-            strncpy(DEBUG_FILENAME_STRING, testName.c_str(), DEBUG_FILENAME_STRING_LENGTH);
-        } else if (fTestStep == kEncodeFiles) {
-            strncpy(DEBUG_FILENAME_STRING, "", DEBUG_FILENAME_STRING_LENGTH);
-        }
-    #endif
-        SkString path = get_in_path(fDirNo, fFilename);
-        SkFILEStream stream(path.c_str());
-        if (!stream.isValid()) {
-            SkDebugf("invalid stream %s\n", path.c_str());
-            return;
-        }
-        pic = SkPicture::MakeFromStream(&stream);
-        if (!pic) {
-            SkDebugf("unable to decode %s\n", fFilename);
-            return;
-        }
-        SkScalar width = pic->cullRect().width();
-        SkScalar height = pic->cullRect().height();
-        SkBitmap oldBitmap, opBitmap;
-        fScale = 1;
-        while (width / fScale > 32767 || height / fScale > 32767) {
-            ++fScale;
-        }
-        do {
-            int dimX = SkScalarCeilToInt(width / fScale);
-            int dimY = SkScalarCeilToInt(height / fScale);
-            if (oldBitmap.tryAllocN32Pixels(dimX, dimY) && opBitmap.tryAllocN32Pixels(dimX, dimY)) {
-                break;
-            }
-            SkDebugf("-%d-", fScale);
-        } while (++fScale < 256);
-        if (fScale >= 256) {
-            SkDebugf("unable to allocate bitmap for %s (w=%f h=%f)\n", fFilename,
-                    width, height);
-            return;
-        }
-        oldBitmap.eraseColor(SK_ColorWHITE);
-        SkCanvas oldCanvas(oldBitmap);
-        oldCanvas.setAllowSimplifyClip(false);
-        opBitmap.eraseColor(SK_ColorWHITE);
-        SkCanvas opCanvas(opBitmap);
-        opCanvas.setAllowSimplifyClip(true);
-        drawPict(pic.get(), &oldCanvas, fScale);
-        drawPict(pic.get(), &opCanvas, fScale);
-        if (fTestStep == kCompareBits) {
-            fPixelError = similarBits(oldBitmap, opBitmap);
-            SkMSec oldTime = timePict(pic.get(), &oldCanvas);
-            SkMSec opTime = timePict(pic.get(), &opCanvas);
-            fTime = SkTMax(static_cast<SkMSec>(0), oldTime - opTime);
-        } else if (fTestStep == kEncodeFiles) {
-            SkString pngStr = make_png_name(fFilename);
-            const char* pngName = pngStr.c_str();
-            writePict(oldBitmap, outOldDir, pngName);
-            writePict(opBitmap, outOpDir, pngName);
-        }
-    }
-}
-
-DEFINE_string2(match, m, "PathOpsSkpClipThreaded",
-        "[~][^]substring[$] [...] of test name to run.\n"
-        "Multiple matches may be separated by spaces.\n"
-        "~ causes a matching test to always be skipped\n"
-        "^ requires the start of the test to match\n"
-        "$ requires the end of the test to match\n"
-        "^ and $ requires an exact match\n"
-        "If a test does not match any list entry,\n"
-        "it is skipped unless some list entry starts with ~");
-DEFINE_string2(dir, d, nullptr, "range of directories (e.g., 1-100)");
-DEFINE_string2(skp, s, nullptr, "skp to test");
-DEFINE_bool2(single, z, false, "run tests on a single thread internally.");
-DEFINE_int32(testIndex, 0, "override local test index (PathOpsSkpClipOneOff only).");
-DEFINE_bool2(verbose, v, false, "enable verbose output.");
-
-static bool verbose() {
-    return FLAGS_verbose;
-}
-
-class Dirs {
-public:
-    Dirs() {
-        reset();
-        sk_bzero(fRun, sizeof(fRun));
-        fSet = false;
-    }
-
-    int first() const {
-        int index = 0;
-        while (++index < kMaxDir) {
-            if (fRun[index]) {
-                return index;
-            }
-        }
-        SkASSERT(0);
-        return -1;
-    }
-
-    int last() const {
-        int index = kMaxDir;
-        while (--index > 0 && !fRun[index])
-            ;
-        return index;
-    }
-
-    int next() {
-        while (++fIndex < kMaxDir) {
-            if (fRun[fIndex]) {
-                return fIndex;
-            }
-        }
-        return -1;
-    }
-
-    void reset() {
-        fIndex = -1;
-    }
-
-    void set(int start, int end) {
-        while (start < end) {
-            fRun[start++] = 1;
-        }
-        fSet = true;
-    }
-
-    void setDefault() {
-        if (!fSet) {
-            set(1, 100);
-        }
-    }
-
-private:
-    enum {
-         kMaxDir = 101
-    };
-    char fRun[kMaxDir];
-    int fIndex;
-    bool fSet;
-} gDirs;
-
-class Filenames {
-public:
-    Filenames()
-        : fIndex(-1) {
-    }
-
-    const char* next() {
-        while (fNames && ++fIndex < fNames->count()) {
-            return (*fNames)[fIndex];
-        }
-        return nullptr;
-    }
-
-    void set(const SkCommandLineFlags::StringArray& names) {
-        fNames = &names;
-    }
-
-private:
-    int fIndex;
-    const SkCommandLineFlags::StringArray* fNames;
-} gNames;
-
-static bool buildTestDir(int dirNo, int firstDirNo,
-        SkTDArray<TestResult>* tests, SkTDArray<SortByName*>* sorted) {
-    SkString dirName = get_out_path(dirNo, outStatusDir);
-    if (!dirName.size()) {
-        return false;
-    }
-    SkOSFile::Iter iter(dirName.c_str(), "skp");
-    SkString filename;
-    while (iter.next(&filename)) {
-        TestResult test;
-        test.init(dirNo);
-        SkString spaceFile(filename);
-        char* spaces = spaceFile.writable_str();
-        int spaceSize = (int) spaceFile.size();
-        for (int index = 0; index < spaceSize; ++index) {
-            if (spaces[index] == '.') {
-                spaces[index] = ' ';
-            }
-        }
-        int success = sscanf(spaces, "%s %d %d skp", test.fFilename,
-                &test.fPixelError, &test.fTime);
-        if (success < 3) {
-            SkDebugf("failed to scan %s matched=%d\n", filename.c_str(), success);
-            return false;
-        }
-        *tests[dirNo - firstDirNo].append() = test;
-    }
-    if (!sorted) {
-        return true;
-    }
-    SkTDArray<TestResult>& testSet = tests[dirNo - firstDirNo];
-    int count = testSet.count();
-    for (int index = 0; index < count; ++index) {
-        *sorted[dirNo - firstDirNo].append() = (SortByName*) &testSet[index];
-    }
-    if (sorted[dirNo - firstDirNo].count()) {
-        SkTQSort<SortByName>(sorted[dirNo - firstDirNo].begin(),
-                sorted[dirNo - firstDirNo].end() - 1);
-        if (verbose()) {
-            SkDebugf("+");
-        }
-    }
-    return true;
-}
-
-static void testSkpClip(TestState* data) {
-    data->fResult.testOne();
-    SkString statName(data->fResult.fFilename);
-    SkASSERT(statName.endsWith(".skp"));
-    statName.remove(statName.size() - 4, 4);
-    statName.appendf(".%d.%d.skp", data->fResult.fPixelError, data->fResult.fTime);
-    SkString statusFile = get_out_path(data->fResult.fDirNo, outStatusDir);
-    if (!statusFile.size()) {
-        SkDebugf("failed to create %s", statusFile.c_str());
-        return;
-    }
-    statusFile.appendf("%s%s", PATH_SLASH, statName.c_str());
-    FILE* file = sk_fopen(statusFile.c_str(), kWrite_SkFILE_Flag);
-    if (!file) {
-            SkDebugf("failed to create %s", statusFile.c_str());
-            return;
-    }
-    sk_fclose(file);
-    if (verbose()) {
-        if (data->fResult.fPixelError || data->fResult.fTime) {
-            SkDebugf("%s", data->fResult.progress().c_str());
-        } else {
-            SkDebugf(".");
-        }
-    }
-}
-
-bool Less(const SortByName& a, const SortByName& b);
-bool Less(const SortByName& a, const SortByName& b) {
-    return a < b;
-}
-
-static bool doOneDir(TestState* state, bool threaded) {
-    int dirNo = state->fResult.fDirNo;
-    SkString dirName = get_in_path(dirNo, nullptr);
-    if (!dirName.size()) {
-        return false;
-    }
-    SkTDArray<TestResult> tests[1];
-    SkTDArray<SortByName*> sorted[1];
-    if (!buildTestDir(dirNo, dirNo, tests, sorted)) {
-        return false;
-    }
-    SkOSFile::Iter iter(dirName.c_str(), "skp");
-    SkString filename;
-    while (iter.next(&filename)) {
-        for (size_t index = 0; index < skipOverCount; ++index) {
-            if (skipOver[index].directory == dirNo
-                    && strcmp(filename.c_str(), skipOver[index].filename) == 0) {
-                goto checkEarlyExit;
-            }
-        }
-        {
-            SortByName name;
-            name.init(dirNo);
-            strncpy(name.fFilename, filename.c_str(), filename.size() - 4);  // drop .skp
-            int count = sorted[0].count();
-            int idx = SkTSearch<SortByName, Less>(sorted[0].begin(), count, &name, sizeof(&name));
-            if (idx >= 0) {
-                SortByName* found = sorted[0][idx];
-                (void) addError(state, *found);
-                continue;
-            }
-            TestResult test;
-            test.init(dirNo, filename);
-            state->fResult = test;
-            testSkpClip(state);
-#if 0 // artificially limit to a few while debugging code
-            static int debugLimit = 0;
-            if (++debugLimit == 5) {
-                return true;
-            }
-#endif
-        }
-checkEarlyExit:
-        ;
-    }
-    return true;
-}
-
-static void testSkpClipEncode(TestState* data) {
-    data->fResult.testOne();
-    if (verbose()) {
-        SkDebugf("+");
-    }
-}
-
-static void encodeFound(TestState& state) {
-    if (verbose()) {
-        if (state.fPixelWorst.count()) {
-            SkTDArray<SortByPixel*> worst;
-            for (int index = 0; index < state.fPixelWorst.count(); ++index) {
-                *worst.append() = &state.fPixelWorst[index];
-            }
-            SkTQSort<SortByPixel>(worst.begin(), worst.end() - 1);
-            for (int index = 0; index < state.fPixelWorst.count(); ++index) {
-                const TestResult& result = *worst[index];
-                SkDebugf("%d %s pixelError=%d\n", result.fDirNo, result.fFilename, result.fPixelError);
-            }
-        }
-        if (state.fSlowest.count()) {
-            SkTDArray<SortByTime*> slowest;
-            for (int index = 0; index < state.fSlowest.count(); ++index) {
-                *slowest.append() = &state.fSlowest[index];
-            }
-            if (slowest.count() > 0) {
-                SkTQSort<SortByTime>(slowest.begin(), slowest.end() - 1);
-                for (int index = 0; index < slowest.count(); ++index) {
-                    const TestResult& result = *slowest[index];
-                    SkDebugf("%d %s time=%d\n", result.fDirNo, result.fFilename, result.fTime);
-                }
-            }
-        }
-    }
-    TestRunner testRunner;
-    for (int index = 0; index < state.fPixelWorst.count(); ++index) {
-        const TestResult& result = state.fPixelWorst[index];
-        SkString filename(result.fFilename);
-        if (!filename.endsWith(".skp")) {
-            filename.append(".skp");
-        }
-        *testRunner.fRunnables.append() = new TestRunnableEncode(&testSkpClipEncode, result.fDirNo,
-                                                                 filename.c_str(), &testRunner);
-    }
-    testRunner.render();
-}
-
-class Test {
-public:
-    Test() {}
-    virtual ~Test() {}
-
-    const char* getName() { onGetName(&fName); return fName.c_str(); }
-    void run() { onRun(); }
-
-protected:
-    virtual void onGetName(SkString*) = 0;
-    virtual void onRun() = 0;
-
-private:
-    SkString    fName;
-};
-
-typedef SkTRegistry<Test*(*)(void*)> TestRegistry;
-
-#define DEF_TEST(name)                                                \
-    static void test_##name();                                        \
-    class name##Class : public Test {                                 \
-    public:                                                           \
-        static Test* Factory(void*) { return new name##Class; }       \
-                                                                      \
-    protected:                                                        \
-        void onGetName(SkString* name) override { name->set(#name); } \
-        void onRun() override { test_##name(); }                      \
-    };                                                                \
-    static TestRegistry gReg_##name##Class(name##Class::Factory);     \
-    static void test_##name()
-
-DEF_TEST(PathOpsSkpClip) {
-    gDirs.setDefault();
-    SkTArray<TestResult, true> errors;
-    TestState state;
-    state.init(0);
-    int dirNo;
-    gDirs.reset();
-    while ((dirNo = gDirs.next()) > 0) {
-        if (verbose()) {
-            SkDebugf("dirNo=%d\n", dirNo);
-        }
-        state.fResult.fDirNo = dirNo;
-        if (!doOneDir(&state, false)) {
-            break;
-        }
-    }
-    encodeFound(state);
-}
-
-static void testSkpClipMain(TestState* data) {
-        (void) doOneDir(data, true);
-}
-
-DEF_TEST(PathOpsSkpClipThreaded) {
-    gDirs.setDefault();
-    TestRunner testRunner;
-    int dirNo;
-    gDirs.reset();
-    while ((dirNo = gDirs.next()) > 0) {
-        *testRunner.fRunnables.append() = new TestRunnableDir(&testSkpClipMain, dirNo, &testRunner);
-    }
-    testRunner.render();
-    TestState state;
-    state.init(0);
-    gDirs.reset();
-    while ((dirNo = gDirs.next()) > 0) {
-        TestState& testState = testRunner.fRunnables[dirNo - 1]->fState;
-        SkASSERT(testState.fResult.fDirNo == dirNo);
-        for (int inner = 0; inner < testState.fPixelWorst.count(); ++inner) {
-            addError(&state, testState.fPixelWorst[inner]);
-        }
-        for (int inner = 0; inner < testState.fSlowest.count(); ++inner) {
-            addError(&state, testState.fSlowest[inner]);
-        }
-    }
-    encodeFound(state);
-}
-
-static bool buildTests(SkTDArray<TestResult>* tests, SkTDArray<SortByName*>* sorted) {
-    int firstDirNo = gDirs.first();
-    int dirNo;
-    while ((dirNo = gDirs.next()) > 0) {
-        if (!buildTestDir(dirNo, firstDirNo, tests, sorted)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-DEF_TEST(PathOpsSkpClipUberThreaded) {
-    gDirs.setDefault();
-    const int firstDirNo = gDirs.next();
-    const int lastDirNo = gDirs.last();
-    int dirCount = lastDirNo - firstDirNo + 1;
-    std::unique_ptr<SkTDArray<TestResult>[]> tests(new SkTDArray<TestResult>[dirCount]);
-    std::unique_ptr<SkTDArray<SortByName*>[]> sorted(new SkTDArray<SortByName*>[dirCount]);
-    if (!buildTests(tests.get(), sorted.get())) {
-        return;
-    }
-    TestRunner testRunner;
-    int dirNo;
-    gDirs.reset();
-    while ((dirNo = gDirs.next()) > 0) {
-        SkString dirName = get_in_path(dirNo, nullptr);
-        if (!dirName.size()) {
-            continue;
-        }
-        SkOSFile::Iter iter(dirName.c_str(), "skp");
-        SkString filename;
-        while (iter.next(&filename)) {
-            for (size_t index = 0; index < skipOverCount; ++index) {
-                if (skipOver[index].directory == dirNo
-                        && strcmp(filename.c_str(), skipOver[index].filename) == 0) {
-                    goto checkEarlyExit;
-                }
-            }
-            {
-                SortByName name;
-                name.init(dirNo);
-                strncpy(name.fFilename, filename.c_str(), filename.size() - 4);  // drop .skp
-                int count = sorted.get()[dirNo - firstDirNo].count();
-                if (SkTSearch<SortByName, Less>(sorted.get()[dirNo - firstDirNo].begin(),
-                        count, &name, sizeof(&name)) < 0) {
-                    *testRunner.fRunnables.append() = new TestRunnableFile(
-                            &testSkpClip, dirNo, filename.c_str(), &testRunner);
-                }
-            }
-    checkEarlyExit:
-            ;
-        }
-
-    }
-    testRunner.render();
-    std::unique_ptr<SkTDArray<TestResult>[]> results(new SkTDArray<TestResult>[dirCount]);
-    if (!buildTests(results.get(), nullptr)) {
-        return;
-    }
-    SkTDArray<TestResult> allResults;
-    for (int dirNo = firstDirNo; dirNo <= lastDirNo; ++dirNo) {
-        SkTDArray<TestResult>& array = results.get()[dirNo - firstDirNo];
-        allResults.append(array.count(), array.begin());
-    }
-    int allCount = allResults.count();
-    SkTDArray<SortByPixel*> pixels;
-    SkTDArray<SortByTime*> times;
-    for (int index = 0; index < allCount; ++index) {
-        *pixels.append() = (SortByPixel*) &allResults[index];
-        *times.append() = (SortByTime*) &allResults[index];
-    }
-    TestState state;
-    if (pixels.count()) {
-        SkTQSort<SortByPixel>(pixels.begin(), pixels.end() - 1);
-        for (int inner = 0; inner < kMaxFiles; ++inner) {
-            *state.fPixelWorst.append() = *pixels[allCount - inner - 1];
-        }
-    }
-    if (times.count()) {
-        SkTQSort<SortByTime>(times.begin(), times.end() - 1);
-        for (int inner = 0; inner < kMaxFiles; ++inner) {
-            *state.fSlowest.append() = *times[allCount - inner - 1];
-        }
-    }
-    encodeFound(state);
-}
-
-DEF_TEST(PathOpsSkpClipOneOff) {
-    const int testIndex = FLAGS_testIndex;
-    int dirNo = gDirs.next();
-    if (dirNo < 0) {
-        dirNo = skipOver[testIndex].directory;
-    }
-    const char* skp = gNames.next();
-    if (!skp) {
-        skp = skipOver[testIndex].filename;
-    }
-    SkAssertResult(get_in_path(dirNo, skp).size());
-    SkString filename(skp);
-    TestResult state;
-    state.test(dirNo, filename);
-    if (verbose()) {
-        SkDebugf("%s", state.status().c_str());
-    }
-    state.fTestStep = kEncodeFiles;
-    state.testOne();
-}
-
-DEF_TEST(PathOpsTestSkipped) {
-    for (size_t index = 0; index < skipOverCount; ++index) {
-        const SkipOverTest& skip = skipOver[index];
-        if (!skip.blamePathOps) {
-            continue;
-        }
-        int dirNo = skip.directory;
-        const char* skp = skip.filename;
-        SkAssertResult(get_in_path(dirNo, skp).size());
-        SkString filename(skp);
-        TestResult state;
-        state.test(dirNo, filename);
-        if (verbose()) {
-            SkDebugf("%s", state.status().c_str());
-        }
-        state.fTestStep = kEncodeFiles;
-        state.testOne();
-    }
-}
-
-DEF_TEST(PathOpsCopyFails) {
-    FLAGS_verbose = true;
-    for (size_t index = 0; index < skipOverCount; ++index) {
-        int dirNo = skipOver[index].directory;
-        SkDebugf("mkdir -p " IN_DIR_PRE "%d" DIR_POST "\n", dirNo);
-    }
-    for (size_t index = 0; index < skipOverCount; ++index) {
-        int dirNo = skipOver[index].directory;
-        const char* filename = skipOver[index].filename;
-        SkDebugf("rsync -av cary-linux.cnc:/tera" PATH_SLASH "skps" PATH_SLASH "slave"
-            "%d" DIR_POST "/%s " IN_DIR_PRE "%d" DIR_POST "\n", dirNo, filename, dirNo);
-    }
-}
-
-template TestRegistry* TestRegistry::gHead;
-
-class Iter {
-public:
-    Iter() { this->reset(); }
-    void reset() { fReg = TestRegistry::Head(); }
-
-    Test* next() {
-        if (fReg) {
-            TestRegistry::Factory fact = fReg->factory();
-            fReg = fReg->next();
-            Test* test = fact(nullptr);
-            return test;
-        }
-        return nullptr;
-    }
-
-private:
-    const TestRegistry* fReg;
-};
-
-int tool_main(int argc, char** argv);
-int tool_main(int argc, char** argv) {
-    SetupCrashHandler();
-    SkCommandLineFlags::SetUsage("");
-    SkCommandLineFlags::Parse(argc, argv);
-    SkGraphics::Init();
-    SkString header("PathOps SkpClip:");
-    if (!FLAGS_match.isEmpty()) {
-        header.appendf(" --match");
-        for (int index = 0; index < FLAGS_match.count(); ++index) {
-            header.appendf(" %s", FLAGS_match[index]);
-        }
-    }
-    if (!FLAGS_dir.isEmpty()) {
-        int count = FLAGS_dir.count();
-        for (int i = 0; i < count; ++i) {
-            const char* range = FLAGS_dir[i];
-            const char* dash = strchr(range, '-');
-            if (!dash) {
-                dash = strchr(range, ',');
-            }
-            int first = atoi(range);
-            int last = dash ? atoi(dash + 1) : first;
-            if (!first || !last) {
-                SkDebugf("couldn't parse --dir %s\n", range);
-                return 1;
-            }
-            gDirs.set(first, last);
-        }
-    }
-    if (!FLAGS_skp.isEmpty()) {
-        gNames.set(FLAGS_skp);
-    }
-#ifdef SK_DEBUG
-    header.append(" SK_DEBUG");
-#else
-    header.append(" SK_RELEASE");
-#endif
-    if (FLAGS_verbose) {
-        header.appendf("\n");
-    }
-    SkDebugf("%s", header.c_str());
-    Iter iter;
-    Test* test;
-    while ((test = iter.next()) != nullptr) {
-        std::unique_ptr<Test> owned(test);
-        if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, test->getName())) {
-            test->run();
-        }
-    }
-    return 0;
-}
-
-#if !defined(SK_BUILD_FOR_IOS)
-int main(int argc, char * const argv[]) {
-    return tool_main(argc, (char**) argv);
-}
-#endif
diff --git a/tests/PathOpsThreadedCommon.h b/tests/PathOpsThreadedCommon.h
index 54586d9..6cc0c3f4 100644
--- a/tests/PathOpsThreadedCommon.h
+++ b/tests/PathOpsThreadedCommon.h
@@ -8,6 +8,8 @@
 #define PathOpsThreadedCommon_DEFINED
 
 #include "SkGraphics.h"
+#include "SkPath.h"
+#include "SkString.h"
 #include "SkTDArray.h"
 
 #define PATH_STR_SIZE 512
@@ -23,11 +25,14 @@
     unsigned char fB;
     unsigned char fC;
     unsigned char fD;
-    char* fPathStr;
+    SkString fPathStr;
     const char* fKey;
     char fSerialNo[256];
     skiatest::Reporter* fReporter;
     SkBitmap* fBitmap;
+
+    void outputProgress(const char* pathStr, SkPath::FillType);
+    void outputProgress(const char* pathStr, SkPathOp);
 };
 
 class PathOpsThreadedTestRunner {
@@ -75,8 +80,6 @@
     void operator()() {
         SkBitmap bitmap;
         fState.fBitmap = &bitmap;
-        char pathStr[PATH_STR_SIZE];
-        fState.fPathStr = pathStr;
         SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
         (*fTestFun)(&fState);
     }
diff --git a/tests/PathOpsTightBoundsTest.cpp b/tests/PathOpsTightBoundsTest.cpp
index 8fd0fdb..a2e7bca 100644
--- a/tests/PathOpsTightBoundsTest.cpp
+++ b/tests/PathOpsTightBoundsTest.cpp
@@ -188,3 +188,14 @@
     REPORTER_ASSERT(reporter, bounds != tight);
 }
 
+DEF_TEST(PathOpsTightBoundsIllBehavedScaled, reporter) {
+    SkPath path;
+    path.moveTo(0, 0);
+    path.quadTo(1048578, 1048577, 1048576, 1048576);
+    const SkRect& bounds = path.getBounds();
+    SkRect tight;
+    REPORTER_ASSERT(reporter, TightBounds(path, &tight));
+    REPORTER_ASSERT(reporter, bounds != tight);
+    REPORTER_ASSERT(reporter, tight.right() == 1048576);
+    REPORTER_ASSERT(reporter, tight.bottom() == 1048576);
+}
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 9a44d29..0ace812 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -5,14 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include <cmath>
+#include "SkAutoMalloc.h"
 #include "SkCanvas.h"
 #include "SkGeometry.h"
 #include "SkPaint.h"
 #include "SkParse.h"
 #include "SkParsePath.h"
-#include "SkPathPriv.h"
 #include "SkPathEffect.h"
+#include "SkPathPriv.h"
 #include "SkRRect.h"
 #include "SkRandom.h"
 #include "SkReader32.h"
@@ -20,9 +20,9 @@
 #include "SkStream.h"
 #include "SkStrokeRec.h"
 #include "SkSurface.h"
-#include "SkTypes.h"
 #include "SkWriter32.h"
 #include "Test.h"
+#include <cmath>
 
 static void set_radii(SkVector radii[4], int index, float rad) {
     sk_bzero(radii, sizeof(SkVector) * 4);
@@ -2532,6 +2532,69 @@
     REPORTER_ASSERT(reporter, origBounds == readBackBounds);
 }
 
+static void test_corrupt_flattening(skiatest::Reporter* reporter) {
+    SkPath path;
+    path.moveTo(1, 2);
+    path.lineTo(1, 2);
+    path.quadTo(1, 2, 3, 4);
+    path.conicTo(1, 2, 3, 4, 0.5f);
+    path.cubicTo(1, 2, 3, 4, 5, 6);
+    uint8_t buffer[1024];
+    SkDEBUGCODE(size_t size =) path.writeToMemory(buffer);
+    SkASSERT(size <= sizeof(buffer));
+    
+    // find where the counts and verbs are stored : from the impl in SkPathRef.cpp
+    int32_t* vCount = (int32_t*)&buffer[16];
+    SkASSERT(*vCount == 5);
+    int32_t* pCount = (int32_t*)&buffer[20];
+    SkASSERT(*pCount == 9);
+    int32_t* cCount = (int32_t*)&buffer[24];
+    SkASSERT(*cCount == 1);
+    uint8_t* verbs = &buffer[28];
+    
+    REPORTER_ASSERT(reporter, path.readFromMemory(buffer, sizeof(buffer)));
+    
+    // check that we detect under/over-flow of counts
+    
+    *vCount += 1;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    *vCount -= 1;   // restore
+    
+    *pCount += 1;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    *pCount -= 2;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    *pCount += 1;   // restore
+    
+    *cCount += 1;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    *cCount -= 2;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    *cCount += 1;   // restore
+    
+    // Check that we detect when the verbs indicate more or fewer pts/conics
+    
+    uint8_t save = verbs[0];
+    SkASSERT(save == SkPath::kCubic_Verb);
+    verbs[0] = SkPath::kQuad_Verb;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    verbs[0] = save;
+    
+    save = verbs[1];
+    SkASSERT(save == SkPath::kConic_Verb);
+    verbs[1] = SkPath::kQuad_Verb;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    verbs[1] = SkPath::kCubic_Verb;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    verbs[1] = save;
+    
+    // Check that we detect invalid verbs
+    save = verbs[1];
+    verbs[1] = 17;
+    REPORTER_ASSERT(reporter, !path.readFromMemory(buffer, sizeof(buffer)));
+    verbs[1] = save;
+}
+
 static void test_flattening(skiatest::Reporter* reporter) {
     SkPath p;
 
@@ -2580,6 +2643,8 @@
 
         write_and_read_back(reporter, oval);
     }
+
+    test_corrupt_flattening(reporter);
 }
 
 static void test_transform(skiatest::Reporter* reporter) {
diff --git a/tests/PrimitiveProcessorTest.cpp b/tests/PrimitiveProcessorTest.cpp
index 9cb2f8a..a0b3a35 100644
--- a/tests/PrimitiveProcessorTest.cpp
+++ b/tests/PrimitiveProcessorTest.cpp
@@ -31,7 +31,9 @@
 
     const char* name() const override { return "Dummy Op"; }
 
-    static sk_sp<GrDrawOp> Make(int numAttribs) { return sk_sp<GrDrawOp>(new Op(numAttribs)); }
+    static std::unique_ptr<GrDrawOp> Make(int numAttribs) {
+        return std::unique_ptr<GrDrawOp>(new Op(numAttribs));
+    }
 
 private:
     Op(int numAttribs) : INHERITED(ClassID()), fNumAttribs(numAttribs) {
@@ -123,7 +125,7 @@
 #endif
     GrPaint grPaint;
     // This one should succeed.
-    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
+    renderTargetContext->priv().testingOnly_addDrawOp(GrPaint(grPaint), GrAAType::kNone,
                                                       Op::Make(attribCnt));
     context->flush();
 #if GR_GPU_STATS
@@ -131,7 +133,7 @@
     REPORTER_ASSERT(reporter, context->getGpu()->stats()->numFailedDraws() == 0);
 #endif
     context->resetGpuStats();
-    renderTargetContext->priv().testingOnly_addDrawOp(grPaint, GrAAType::kNone,
+    renderTargetContext->priv().testingOnly_addDrawOp(std::move(grPaint), GrAAType::kNone,
                                                       Op::Make(attribCnt + 1));
     context->flush();
 #if GR_GPU_STATS
diff --git a/tests/ProcessorTest.cpp b/tests/ProcessorTest.cpp
new file mode 100644
index 0000000..e18a163
--- /dev/null
+++ b/tests/ProcessorTest.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+#include "Test.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrGpuResource.h"
+#include "GrRenderTargetContext.h"
+#include "GrRenderTargetContextPriv.h"
+#include "GrResourceProvider.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "ops/GrTestMeshDrawOp.h"
+
+namespace {
+class TestOp : public GrTestMeshDrawOp {
+public:
+    DEFINE_OP_CLASS_ID
+    const char* name() const override { return "TestOp"; }
+
+    static std::unique_ptr<GrDrawOp> Make() { return std::unique_ptr<GrDrawOp>(new TestOp); }
+
+private:
+    TestOp() : INHERITED(ClassID(), SkRect::MakeWH(100, 100), 0xFFFFFFFF) {}
+
+    void onPrepareDraws(Target* target) const override { return; }
+
+    typedef GrTestMeshDrawOp INHERITED;
+};
+
+/**
+ * FP used to test ref/IO counts on owned GrGpuResources. Can also be a parent FP to test counts
+ * of resources owned by child FPs.
+ */
+class TestFP : public GrFragmentProcessor {
+public:
+    struct Image {
+        Image(sk_sp<GrTexture> texture, GrIOType ioType) : fTexture(texture), fIOType(ioType) {}
+        sk_sp<GrTexture> fTexture;
+        GrIOType fIOType;
+    };
+    static sk_sp<GrFragmentProcessor> Make(sk_sp<GrFragmentProcessor> child) {
+        return sk_sp<GrFragmentProcessor>(new TestFP(std::move(child)));
+    }
+    static sk_sp<GrFragmentProcessor> Make(const SkTArray<sk_sp<GrTexture>>& textures,
+                                           const SkTArray<sk_sp<GrBuffer>>& buffers,
+                                           const SkTArray<Image>& images) {
+        return sk_sp<GrFragmentProcessor>(new TestFP(textures, buffers, images));
+    }
+
+    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));
+    }
+
+    void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
+        // We don't care about optimizing these processors.
+        inout->setToUnknown(GrInvariantOutput::kWill_ReadInput);
+    }
+
+private:
+    TestFP(const SkTArray<sk_sp<GrTexture>>& textures, const SkTArray<sk_sp<GrBuffer>>& buffers,
+           const SkTArray<Image>& images)
+            : fSamplers(4), fBuffers(4), fImages(4) {
+        for (const auto& texture : textures) {
+            this->addTextureSampler(&fSamplers.emplace_back(texture.get()));
+        }
+        for (const auto& buffer : buffers) {
+            this->addBufferAccess(&fBuffers.emplace_back(kRGBA_8888_GrPixelConfig, buffer.get()));
+        }
+        for (const Image& image : images) {
+            this->addImageStorageAccess(&fImages.emplace_back(
+                    image.fTexture, image.fIOType, GrSLMemoryModel::kNone, GrSLRestrict::kNo));
+        }
+    }
+
+    TestFP(sk_sp<GrFragmentProcessor> child) : fSamplers(4), fBuffers(4), fImages(4) {
+        this->registerChildProcessor(std::move(child));
+    }
+
+    virtual GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
+        class TestGLSLFP : public GrGLSLFragmentProcessor {
+        public:
+            TestGLSLFP() {}
+            void emitCode(EmitArgs& args) override {
+                GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+                fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, args.fInputColor);
+            }
+
+        private:
+        };
+        return new TestGLSLFP();
+    }
+
+    bool onIsEqual(const GrFragmentProcessor&) const override { return false; }
+
+    GrTAllocator<TextureSampler> fSamplers;
+    GrTAllocator<BufferAccess> fBuffers;
+    GrTAllocator<ImageStorageAccess> fImages;
+};
+}
+
+template <typename T>
+inline void testingOnly_getIORefCnts(const T* resource, int* refCnt, int* readCnt, int* writeCnt) {
+    *refCnt = resource->fRefCnt;
+    *readCnt = resource->fPendingReads;
+    *writeCnt = resource->fPendingWrites;
+}
+
+DEF_GPUTEST_FOR_ALL_CONTEXTS(ProcessorRefTest, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+
+    GrTextureDesc desc;
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    desc.fWidth = 10;
+    desc.fHeight = 10;
+
+    for (int parentCnt = 0; parentCnt < 2; parentCnt++) {
+        sk_sp<GrRenderTargetContext> renderTargetContext(context->makeRenderTargetContext(
+                SkBackingFit::kApprox, 1, 1, kRGBA_8888_GrPixelConfig, nullptr));
+        {
+            bool texelBufferSupport = context->caps()->shaderCaps()->texelBufferSupport();
+            bool imageLoadStoreSupport = context->caps()->shaderCaps()->imageLoadStoreSupport();
+            sk_sp<GrTexture> texture1(
+                    context->resourceProvider()->createTexture(desc, SkBudgeted::kYes));
+            sk_sp<GrTexture> texture2(
+                    context->resourceProvider()->createTexture(desc, SkBudgeted::kYes));
+            sk_sp<GrTexture> texture3(
+                    context->resourceProvider()->createTexture(desc, SkBudgeted::kYes));
+            sk_sp<GrTexture> texture4(
+                    context->resourceProvider()->createTexture(desc, SkBudgeted::kYes));
+            sk_sp<GrBuffer> buffer(texelBufferSupport
+                                           ? context->resourceProvider()->createBuffer(
+                                                     1024, GrBufferType::kTexel_GrBufferType,
+                                                     GrAccessPattern::kStatic_GrAccessPattern, 0)
+                                           : nullptr);
+            {
+                SkTArray<sk_sp<GrTexture>> textures;
+                SkTArray<sk_sp<GrBuffer>> buffers;
+                SkTArray<TestFP::Image> images;
+                textures.push_back(texture1);
+                if (texelBufferSupport) {
+                    buffers.push_back(buffer);
+                }
+                if (imageLoadStoreSupport) {
+                    images.emplace_back(texture2, GrIOType::kRead_GrIOType);
+                    images.emplace_back(texture3, GrIOType::kWrite_GrIOType);
+                    images.emplace_back(texture4, GrIOType::kRW_GrIOType);
+                }
+                std::unique_ptr<GrDrawOp> op(TestOp::Make());
+                GrPaint paint;
+                auto fp = TestFP::Make(std::move(textures), std::move(buffers), std::move(images));
+                for (int i = 0; i < parentCnt; ++i) {
+                    fp = TestFP::Make(std::move(fp));
+                }
+                paint.addColorFragmentProcessor(std::move(fp));
+                renderTargetContext->priv().testingOnly_addDrawOp(std::move(paint), GrAAType::kNone,
+                                                                  std::move(op));
+            }
+            int refCnt, readCnt, writeCnt;
+
+            testingOnly_getIORefCnts(texture1.get(), &refCnt, &readCnt, &writeCnt);
+            REPORTER_ASSERT(reporter, 1 == refCnt);
+            REPORTER_ASSERT(reporter, 1 == readCnt);
+            REPORTER_ASSERT(reporter, 0 == writeCnt);
+
+            if (texelBufferSupport) {
+                testingOnly_getIORefCnts(buffer.get(), &refCnt, &readCnt, &writeCnt);
+                REPORTER_ASSERT(reporter, 1 == refCnt);
+                REPORTER_ASSERT(reporter, 1 == readCnt);
+                REPORTER_ASSERT(reporter, 0 == writeCnt);
+            }
+
+            if (imageLoadStoreSupport) {
+                testingOnly_getIORefCnts(texture2.get(), &refCnt, &readCnt, &writeCnt);
+                REPORTER_ASSERT(reporter, 1 == refCnt);
+                REPORTER_ASSERT(reporter, 1 == readCnt);
+                REPORTER_ASSERT(reporter, 0 == writeCnt);
+
+                testingOnly_getIORefCnts(texture3.get(), &refCnt, &readCnt, &writeCnt);
+                REPORTER_ASSERT(reporter, 1 == refCnt);
+                REPORTER_ASSERT(reporter, 0 == readCnt);
+                REPORTER_ASSERT(reporter, 1 == writeCnt);
+
+                testingOnly_getIORefCnts(texture4.get(), &refCnt, &readCnt, &writeCnt);
+                REPORTER_ASSERT(reporter, 1 == refCnt);
+                REPORTER_ASSERT(reporter, 1 == readCnt);
+                REPORTER_ASSERT(reporter, 1 == writeCnt);
+            }
+
+            context->flush();
+
+            testingOnly_getIORefCnts(texture1.get(), &refCnt, &readCnt, &writeCnt);
+            REPORTER_ASSERT(reporter, 1 == refCnt);
+            REPORTER_ASSERT(reporter, 0 == readCnt);
+            REPORTER_ASSERT(reporter, 0 == writeCnt);
+
+            if (texelBufferSupport) {
+                testingOnly_getIORefCnts(buffer.get(), &refCnt, &readCnt, &writeCnt);
+                REPORTER_ASSERT(reporter, 1 == refCnt);
+                REPORTER_ASSERT(reporter, 0 == readCnt);
+                REPORTER_ASSERT(reporter, 0 == writeCnt);
+            }
+
+            if (texelBufferSupport) {
+                testingOnly_getIORefCnts(texture2.get(), &refCnt, &readCnt, &writeCnt);
+                REPORTER_ASSERT(reporter, 1 == refCnt);
+                REPORTER_ASSERT(reporter, 0 == readCnt);
+                REPORTER_ASSERT(reporter, 0 == writeCnt);
+
+                testingOnly_getIORefCnts(texture3.get(), &refCnt, &readCnt, &writeCnt);
+                REPORTER_ASSERT(reporter, 1 == refCnt);
+                REPORTER_ASSERT(reporter, 0 == readCnt);
+                REPORTER_ASSERT(reporter, 0 == writeCnt);
+
+                testingOnly_getIORefCnts(texture4.get(), &refCnt, &readCnt, &writeCnt);
+                REPORTER_ASSERT(reporter, 1 == refCnt);
+                REPORTER_ASSERT(reporter, 0 == readCnt);
+                REPORTER_ASSERT(reporter, 0 == writeCnt);
+            }
+        }
+    }
+}
+#endif
diff --git a/tests/ReadPixelsTest.cpp b/tests/ReadPixelsTest.cpp
index 71cd8f5..ee762d1 100644
--- a/tests/ReadPixelsTest.cpp
+++ b/tests/ReadPixelsTest.cpp
@@ -449,10 +449,7 @@
                 if (startsWithPixels) {
                     fill_dst_bmp_with_init_data(&bmp);
                     GrPixelConfig dstConfig =
-                            SkImageInfo2GrPixelConfig(gReadPixelsConfigs[c].fColorType,
-                                                      gReadPixelsConfigs[c].fAlphaType,
-                                                      nullptr,
-                                                      *texture->getContext()->caps());
+                            SkImageInfo2GrPixelConfig(bmp.info(), *texture->getContext()->caps());
                     uint32_t flags = 0;
                     if (gReadPixelsConfigs[c].fAlphaType == kUnpremul_SkAlphaType) {
                         flags = GrContext::kUnpremul_PixelOpsFlag;
diff --git a/tests/RectangleTextureTest.cpp b/tests/RectangleTextureTest.cpp
index 5871604..a0c5409 100644
--- a/tests/RectangleTextureTest.cpp
+++ b/tests/RectangleTextureTest.cpp
@@ -118,7 +118,7 @@
         GrPaint paint;
         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
         paint.addColorFragmentProcessor(std::move(fp));
-        rtContext->drawPaint(GrNoClip(), paint, SkMatrix::I());
+        rtContext->drawPaint(GrNoClip(), std::move(paint), SkMatrix::I());
         test_read_pixels(reporter, context, rtContext->asTexture().get(), expectedPixelValues);
     }
 }
diff --git a/tests/RegionTest.cpp b/tests/RegionTest.cpp
index 4a96056..1720822 100644
--- a/tests/RegionTest.cpp
+++ b/tests/RegionTest.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkPath.h"
 #include "SkRandom.h"
 #include "SkRegion.h"
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index dad958f..39f64be 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -229,7 +229,6 @@
     sk_sp<GrTexture> adopted(context->textureProvider()->wrapBackendTexture(
                              desc, kAdopt_GrWrapOwnership));
 
-    printf("\nborrowed: %p, adopted: %p\n", borrowed.get(), adopted.get());
     REPORTER_ASSERT(reporter, borrowed != nullptr && adopted != nullptr);
     if (!borrowed || !adopted) {
         return;
@@ -416,9 +415,10 @@
 }
 
 // Each integer passed as a template param creates a new domain.
-template <int> static void make_unique_key(GrUniqueKey* key, int data) {
+template <int>
+static void make_unique_key(GrUniqueKey* key, int data, const char* tag = nullptr) {
     static GrUniqueKey::Domain d = GrUniqueKey::GenerateDomain();
-    GrUniqueKey::Builder builder(key, d, 1);
+    GrUniqueKey::Builder builder(key, d, 1, tag);
     builder[0] = data;
 }
 
@@ -1325,6 +1325,42 @@
     resource->resourcePriv().removeUniqueKey();
 }
 
+static void test_tags(skiatest::Reporter* reporter) {
+#ifdef SK_DEBUG
+    // We will insert 1 resource with tag "tag1", 2 with "tag2", and so on, up through kLastTagIdx.
+    static constexpr int kLastTagIdx = 10;
+    static constexpr int kNumResources = kLastTagIdx * (kLastTagIdx + 1) / 2;
+
+    Mock mock(kNumResources, kNumResources * TestResource::kDefaultSize);
+    GrContext* context = mock.context();
+    GrResourceCache* cache = mock.cache();
+
+    SkString tagStr;
+    int tagIdx = 0;
+    int currTagCnt = 0;
+
+    for (int i = 0; i < kNumResources; ++i, ++currTagCnt) {
+        sk_sp<GrGpuResource> resource(new TestResource(context->getGpu()));
+        GrUniqueKey key;
+        if (currTagCnt == tagIdx) {
+            tagIdx += 1;
+            currTagCnt = 0;
+            tagStr.printf("tag%d", tagIdx);
+        }
+        make_unique_key<1>(&key, i, tagStr.c_str());
+        resource->resourcePriv().setUniqueKey(key);
+    }
+    SkASSERT(kLastTagIdx == tagIdx);
+    SkASSERT(currTagCnt == kLastTagIdx);
+
+    // Test i = 0 to exercise unused tag string.
+    for (int i = 0; i <= kLastTagIdx; ++i) {
+        tagStr.printf("tag%d", i);
+        REPORTER_ASSERT(reporter, cache->countUniqueKeysWithTag(tagStr.c_str()) == i);
+    }
+#endif
+}
+
 DEF_GPUTEST(ResourceCacheMisc, reporter, factory) {
     // The below tests create their own mock contexts.
     test_no_key(reporter);
@@ -1343,6 +1379,7 @@
     test_large_resource_count(reporter);
     test_custom_data(reporter);
     test_abandoned(reporter);
+    test_tags(reporter);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1445,5 +1482,4 @@
     REPORTER_ASSERT(reporter, kSize*kSize*4+(kSize*kSize*4)/3 == size);
 }
 
-
 #endif
diff --git a/tests/SRGBMipMapTest.cpp b/tests/SRGBMipMapTest.cpp
index afe8167..79d8691 100644
--- a/tests/SRGBMipMapTest.cpp
+++ b/tests/SRGBMipMapTest.cpp
@@ -136,13 +136,13 @@
 
     // 1) Draw texture to S32 surface (should generate/use sRGB mips)
     paint.setGammaCorrect(true);
-    s32RenderTargetContext->drawRect(noClip, paint, GrAA::kNo, SkMatrix::I(), rect);
+    s32RenderTargetContext->drawRect(noClip, GrPaint(paint), GrAA::kNo, SkMatrix::I(), rect);
     read_and_check_pixels(reporter, s32RenderTargetContext->asTexture().get(), expectedSRGB, error,
                           "first render of sRGB");
 
     // 2) Draw texture to L32 surface (should generate/use linear mips)
     paint.setGammaCorrect(false);
-    l32RenderTargetContext->drawRect(noClip, paint, GrAA::kNo, SkMatrix::I(), rect);
+    l32RenderTargetContext->drawRect(noClip, GrPaint(paint), GrAA::kNo, SkMatrix::I(), rect);
 
     // Right now, this test only runs on GL (because Vulkan doesn't support legacy mip-mapping
     // skbug.com/5048). On GL, we may not have sRGB decode support. In that case, rendering sRGB
@@ -162,7 +162,7 @@
 
     // 3) Go back to sRGB
     paint.setGammaCorrect(true);
-    s32RenderTargetContext->drawRect(noClip, paint, GrAA::kNo, SkMatrix::I(), rect);
+    s32RenderTargetContext->drawRect(noClip, std::move(paint), GrAA::kNo, SkMatrix::I(), rect);
     read_and_check_pixels(reporter, s32RenderTargetContext->asTexture().get(), expectedSRGB, error,
                           "re-render as sRGB");
 }
diff --git a/tests/SkRasterPipelineTest.cpp b/tests/SkRasterPipelineTest.cpp
index 50c417f..a3314b8 100644
--- a/tests/SkRasterPipelineTest.cpp
+++ b/tests/SkRasterPipelineTest.cpp
@@ -22,7 +22,9 @@
 
     SkRasterPipeline p;
     p.append(SkRasterPipeline::load_f16, &load_s_ctx);
-    p.append(SkRasterPipeline::load_f16_d, &load_d_ctx);
+    p.append(SkRasterPipeline::move_src_dst);
+    p.append(SkRasterPipeline::load_f16, &load_d_ctx);
+    p.append(SkRasterPipeline::swap);
     p.append(SkRasterPipeline::srcover);
     p.append(SkRasterPipeline::store_f16, &store_ctx);
     p.run(0,0, 1);
@@ -32,6 +34,16 @@
     REPORTER_ASSERT(r, ((result >> 16) & 0xffff) == 0x0000);
     REPORTER_ASSERT(r, ((result >> 32) & 0xffff) == 0x3800);
     REPORTER_ASSERT(r, ((result >> 48) & 0xffff) == 0x3c00);
+
+    // Run again, this time compiling the pipeline.
+    result = 0;
+
+    auto fn = p.compile();
+    fn(0,0, 1);
+    REPORTER_ASSERT(r, ((result >>  0) & 0xffff) == 0x3800);
+    REPORTER_ASSERT(r, ((result >> 16) & 0xffff) == 0x0000);
+    REPORTER_ASSERT(r, ((result >> 32) & 0xffff) == 0x3800);
+    REPORTER_ASSERT(r, ((result >> 48) & 0xffff) == 0x3c00);
 }
 
 DEF_TEST(SkRasterPipeline_empty, r) {
@@ -47,3 +59,34 @@
     p.append(SkRasterPipeline::srcover);
     p.run(0,0, 20);
 }
+
+DEF_TEST(SkRasterPipeline_JIT, r) {
+    // This tests a couple odd corners that a JIT backend can stumble over.
+
+    uint32_t buf[72] = {
+         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+         1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,
+        13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+         0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    };
+
+    const uint32_t* src = buf +  0;
+    uint32_t*       dst = buf + 36;
+
+    // Copy buf[x] to buf[x+36] for x in [15,35).
+    SkRasterPipeline p;
+    p.append(SkRasterPipeline:: load_8888, &src);
+    p.append(SkRasterPipeline::store_8888, &dst);
+    auto fn = p.compile();
+    fn(15, 0, 20);
+
+    for (int i = 0; i < 36; i++) {
+        if (i < 15 || i == 35) {
+            REPORTER_ASSERT(r, dst[i] == 0);
+        } else {
+            REPORTER_ASSERT(r, dst[i] == (uint32_t)(i - 11));
+        }
+    }
+}
diff --git a/tests/SkResourceCacheTest.cpp b/tests/SkResourceCacheTest.cpp
index ed2ff74..922acef 100644
--- a/tests/SkResourceCacheTest.cpp
+++ b/tests/SkResourceCacheTest.cpp
@@ -330,6 +330,7 @@
             canvas->clear(SK_ColorCYAN);
             return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(),
                                             SkISize::Make(10, 10), nullptr, nullptr,
+                                            SkImage::BitDepth::kU8,
                                             SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
         });
     }
diff --git a/tests/SkSLErrorTest.cpp b/tests/SkSLErrorTest.cpp
index e872977..a33e6f1 100644
--- a/tests/SkSLErrorTest.cpp
+++ b/tests/SkSLErrorTest.cpp
@@ -307,6 +307,9 @@
     test_failure(r,
                  "void main() { int x = 5 > 2 ? true : 1.0; }",
                  "error: 1: ternary operator result mismatch: 'bool', 'float'\n1 error\n");
+    test_failure(r,
+                 "void main() { int x = 5 > 2 ? vec3(1) : 1.0; }",
+                 "error: 1: ternary operator result mismatch: 'vec3', 'float'\n1 error\n");
 }
 
 DEF_TEST(SkSLInterfaceBlockStorageModifiers, r) {
@@ -400,4 +403,19 @@
                  "error: 1: offset of field 'y' must be at least 4\n1 error\n");
 }
 
+DEF_TEST(SkSLDivByZero, r) {
+    test_failure(r,
+                 "int x = 1 / 0;",
+                 "error: 1: division by zero\n1 error\n");
+    test_failure(r,
+                 "float x = 1 / 0;",
+                 "error: 1: division by zero\n1 error\n");
+    test_failure(r,
+                 "float x = 1.0 / 0.0;",
+                 "error: 1: division by zero\n1 error\n");
+    test_failure(r,
+                 "float x = -67.0 / (3.0 - 3);",
+                 "error: 1: division by zero\n1 error\n");
+}
+
 #endif
diff --git a/tests/StreamTest.cpp b/tests/StreamTest.cpp
index 4b8923f..fba09c9 100644
--- a/tests/StreamTest.cpp
+++ b/tests/StreamTest.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "Resources.h"
+#include "SkAutoMalloc.h"
 #include "SkData.h"
 #include "SkFrontBufferedStream.h"
 #include "SkOSFile.h"
@@ -146,18 +147,16 @@
 
 
     size_t i;
-    char buffer[sizeof(sizes) * 4];
+    SkDynamicMemoryWStream wstream;
 
-    SkMemoryWStream wstream(buffer, sizeof(buffer));
     for (i = 0; i < SK_ARRAY_COUNT(sizes); ++i) {
         bool success = wstream.writePackedUInt(sizes[i]);
         REPORTER_ASSERT(reporter, success);
     }
-    wstream.flush();
 
-    SkMemoryStream rstream(buffer, sizeof(buffer));
+    std::unique_ptr<SkStreamAsset> rstream(wstream.detachAsStream());
     for (i = 0; i < SK_ARRAY_COUNT(sizes); ++i) {
-        size_t n = rstream.readPackedUInt();
+        size_t n = rstream->readPackedUInt();
         if (sizes[i] != n) {
             ERRORF(reporter, "sizes:%x != n:%x\n", i, sizes[i], n);
         }
@@ -430,3 +429,18 @@
     std::unique_ptr<SkStreamAsset> asset(tmp.detachAsStream());
     REPORTER_ASSERT(r, nullptr == asset->getMemoryBase());
 }
+
+#include "SkBuffer.h"
+
+DEF_TEST(RBuffer, reporter) {
+    int32_t value = 0;
+    SkRBuffer buffer(&value, 4);
+    REPORTER_ASSERT(reporter, buffer.isValid());
+
+    int32_t tmp;
+    REPORTER_ASSERT(reporter, buffer.read(&tmp, 4));
+    REPORTER_ASSERT(reporter, buffer.isValid());
+
+    REPORTER_ASSERT(reporter, !buffer.read(&tmp, 4));
+    REPORTER_ASSERT(reporter, !buffer.isValid());
+}
diff --git a/tests/SurfaceTest.cpp b/tests/SurfaceTest.cpp
index ec8e4a9..1f04de5 100644
--- a/tests/SurfaceTest.cpp
+++ b/tests/SurfaceTest.cpp
@@ -221,134 +221,6 @@
 }
 #endif
 
-static bool same_image(SkImage* a, SkImage* b,
-                       std::function<intptr_t(SkImage*)> getImageBackingStore) {
-    return getImageBackingStore(a) == getImageBackingStore(b);
-}
-
-static bool same_image_surf(SkImage* a, SkSurface* b,
-                            std::function<intptr_t(SkImage*)> getImageBackingStore,
-                            std::function<intptr_t(SkSurface*)> getSurfaceBackingStore) {
-    return getImageBackingStore(a) == getSurfaceBackingStore(b);
-}
-
-static void test_unique_image_snap(skiatest::Reporter* reporter, SkSurface* surface,
-                                   bool surfaceIsDirect,
-                                   std::function<intptr_t(SkImage*)> imageBackingStore,
-                                   std::function<intptr_t(SkSurface*)> surfaceBackingStore) {
-    std::function<intptr_t(SkImage*)> ibs = imageBackingStore;
-    std::function<intptr_t(SkSurface*)> sbs = surfaceBackingStore;
-    static const SkBudgeted kB = SkBudgeted::kNo;
-    {
-        sk_sp<SkImage> image(surface->makeImageSnapshot(kB, SkSurface::kYes_ForceUnique));
-        REPORTER_ASSERT(reporter, !same_image_surf(image.get(), surface, ibs, sbs));
-        REPORTER_ASSERT(reporter, image->unique());
-    }
-    {
-        sk_sp<SkImage> image1(surface->makeImageSnapshot(kB, SkSurface::kYes_ForceUnique));
-        REPORTER_ASSERT(reporter, !same_image_surf(image1.get(), surface, ibs, sbs));
-        REPORTER_ASSERT(reporter, image1->unique());
-        sk_sp<SkImage> image2(surface->makeImageSnapshot(kB, SkSurface::kYes_ForceUnique));
-        REPORTER_ASSERT(reporter, !same_image_surf(image2.get(), surface, ibs, sbs));
-        REPORTER_ASSERT(reporter, !same_image(image1.get(), image2.get(), ibs));
-        REPORTER_ASSERT(reporter, image2->unique());
-    }
-    {
-        sk_sp<SkImage> image1(surface->makeImageSnapshot(kB, SkSurface::kNo_ForceUnique));
-        sk_sp<SkImage> image2(surface->makeImageSnapshot(kB, SkSurface::kYes_ForceUnique));
-        sk_sp<SkImage> image3(surface->makeImageSnapshot(kB, SkSurface::kNo_ForceUnique));
-        sk_sp<SkImage> image4(surface->makeImageSnapshot(kB, SkSurface::kYes_ForceUnique));
-        // Image 1 and 3 ought to be the same (or we're missing an optimization).
-        REPORTER_ASSERT(reporter, same_image(image1.get(), image3.get(), ibs));
-        // If the surface is not direct then images 1 and 3 should alias the surface's
-        // store.
-        REPORTER_ASSERT(reporter, !surfaceIsDirect == same_image_surf(image1.get(), surface, ibs, sbs));
-        // Image 2 should not be shared with any other image.
-        REPORTER_ASSERT(reporter, !same_image(image1.get(), image2.get(), ibs) &&
-                                  !same_image(image3.get(), image2.get(), ibs) &&
-                                  !same_image(image4.get(), image2.get(), ibs));
-        REPORTER_ASSERT(reporter, image2->unique());
-        REPORTER_ASSERT(reporter, !same_image_surf(image2.get(), surface, ibs, sbs));
-        // Image 4 should not be shared with any other image.
-        REPORTER_ASSERT(reporter, !same_image(image1.get(), image4.get(), ibs) &&
-                                  !same_image(image3.get(), image4.get(), ibs));
-        REPORTER_ASSERT(reporter, !same_image_surf(image4.get(), surface, ibs, sbs));
-        REPORTER_ASSERT(reporter, image4->unique());
-    }
-}
-
-DEF_TEST(UniqueImageSnapshot, reporter) {
-    auto getImageBackingStore = [reporter](SkImage* image) {
-        SkPixmap pm;
-        bool success = image->peekPixels(&pm);
-        REPORTER_ASSERT(reporter, success);
-        return reinterpret_cast<intptr_t>(pm.addr());
-    };
-    auto getSufaceBackingStore = [reporter](SkSurface* surface) {
-        SkPixmap pmap;
-        const void* pixels = surface->getCanvas()->peekPixels(&pmap) ? pmap.addr() : nullptr;
-        REPORTER_ASSERT(reporter, pixels);
-        return reinterpret_cast<intptr_t>(pixels);
-    };
-
-    auto surface(create_surface());
-    test_unique_image_snap(reporter, surface.get(), false, getImageBackingStore,
-                           getSufaceBackingStore);
-    surface = create_direct_surface();
-    test_unique_image_snap(reporter, surface.get(), true, getImageBackingStore,
-                           getSufaceBackingStore);
-}
-
-#if SK_SUPPORT_GPU
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(UniqueImageSnapshot_Gpu, reporter, ctxInfo) {
-    GrContext* context = ctxInfo.grContext();
-    for (auto& surface_func : { &create_gpu_surface, &create_gpu_scratch_surface }) {
-        auto surface(surface_func(context, kOpaque_SkAlphaType, nullptr));
-
-        auto imageBackingStore = [reporter](SkImage* image) {
-            GrTexture* texture = as_IB(image)->peekTexture();
-            if (!texture) {
-                ERRORF(reporter, "Not texture backed.");
-                return static_cast<intptr_t>(0);
-            }
-            return static_cast<intptr_t>(texture->uniqueID().asUInt());
-        };
-
-        auto surfaceBackingStore = [reporter](SkSurface* surface) {
-            GrRenderTargetContext* rtc =
-                surface->getCanvas()->internal_private_accessTopLayerRenderTargetContext();
-            GrRenderTarget* rt = rtc->accessRenderTarget();
-            if (!rt) {
-                ERRORF(reporter, "Not render target backed.");
-                return static_cast<intptr_t>(0);
-            }
-            return static_cast<intptr_t>(rt->uniqueID().asUInt());
-        };
-
-        test_unique_image_snap(reporter, surface.get(), false, imageBackingStore,
-                               surfaceBackingStore);
-
-        // Test again with a "direct" render target;
-        GrBackendObject textureObject = context->getGpu()->createTestingOnlyBackendTexture(nullptr,
-            10, 10, kRGBA_8888_GrPixelConfig, true);
-        GrBackendTextureDesc desc;
-        desc.fConfig = kRGBA_8888_GrPixelConfig;
-        desc.fWidth = 10;
-        desc.fHeight = 10;
-        desc.fFlags = kRenderTarget_GrBackendTextureFlag;
-        desc.fTextureHandle = textureObject;
-
-        {
-            sk_sp<SkSurface> surface(SkSurface::MakeFromBackendTexture(context, desc, nullptr));
-            test_unique_image_snap(reporter, surface.get(), true, imageBackingStore,
-                                   surfaceBackingStore);
-        }
-
-        context->getGpu()->deleteTestingOnlyBackendTexture(textureObject);
-    }
-}
-#endif
-
 #if SK_SUPPORT_GPU
 
 static void test_backend_handle_unique_id(
diff --git a/tests/TessellatingPathRendererTests.cpp b/tests/TessellatingPathRendererTests.cpp
index ccc24a4..b03db18 100644
--- a/tests/TessellatingPathRendererTests.cpp
+++ b/tests/TessellatingPathRendererTests.cpp
@@ -254,20 +254,20 @@
     GrTessellatingPathRenderer tess;
 
     GrPaint paint;
-    paint.setXPFactory(GrPorterDuffXPFactory::Make(SkBlendMode::kSrc));
+    paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
 
     GrNoClip noClip;
     GrStyle style(SkStrokeRec::kFill_InitStyle);
-    GrPathRenderer::DrawPathArgs args;
-    args.fPaint = &paint;
-    args.fUserStencilSettings = &GrUserStencilSettings::kUnused;
-    args.fRenderTargetContext = renderTargetContext;
-    args.fClip = &noClip;
-    args.fResourceProvider = rp;
-    args.fViewMatrix = &SkMatrix::I();
     GrShape shape(path, style);
-    args.fShape = &shape;
-    args.fAAType = GrAAType::kNone;
+    GrPathRenderer::DrawPathArgs args{rp,
+                                      std::move(paint),
+                                      &GrUserStencilSettings::kUnused,
+                                      renderTargetContext,
+                                      &noClip,
+                                      &SkMatrix::I(),
+                                      &shape,
+                                      GrAAType::kNone,
+                                      false};
     tess.drawPath(args);
 }
 
diff --git a/tests/Test.h b/tests/Test.h
index 5e6eb6d..db2552b 100644
--- a/tests/Test.h
+++ b/tests/Test.h
@@ -8,7 +8,7 @@
 #define skiatest_Test_DEFINED
 
 #include "SkString.h"
-#include "SkTRegistry.h"
+#include "../tools/Registry.h"
 #include "SkTypes.h"
 #include "SkClipOpPriv.h"
 
@@ -96,7 +96,7 @@
     TestProc proc;
 };
 
-typedef SkTRegistry<Test> TestRegistry;
+typedef sk_tools::Registry<Test> TestRegistry;
 
 /*
     Use the following macros to make use of the skiatest classes, e.g.
diff --git a/tests/UtilsTest.cpp b/tests/UtilsTest.cpp
index b6f90e0..ed861c6 100644
--- a/tests/UtilsTest.cpp
+++ b/tests/UtilsTest.cpp
@@ -219,3 +219,64 @@
     test_autounref(reporter);
     test_autostarray(reporter);
 }
+
+#define ASCII_BYTE         "X"
+#define CONTINUATION_BYTE  "\x80"
+#define LEADING_TWO_BYTE   "\xC4"
+#define LEADING_THREE_BYTE "\xE0"
+#define LEADING_FOUR_BYTE  "\xF0"
+#define INVALID_BYTE       "\xFC"
+static bool valid_utf8(const char* p, size_t l) {
+    return SkUTF8_CountUnicharsWithError(p, l) >= 0;
+}
+DEF_TEST(Utils_UTF8_ValidLength, r) {
+    const char* goodTestcases[] = {
+        "",
+        ASCII_BYTE,
+        ASCII_BYTE ASCII_BYTE,
+        LEADING_TWO_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE LEADING_TWO_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE ASCII_BYTE LEADING_TWO_BYTE CONTINUATION_BYTE,
+        LEADING_THREE_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE LEADING_THREE_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE ASCII_BYTE LEADING_THREE_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        LEADING_FOUR_BYTE CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE LEADING_FOUR_BYTE CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE ASCII_BYTE LEADING_FOUR_BYTE CONTINUATION_BYTE CONTINUATION_BYTE
+            CONTINUATION_BYTE,
+    };
+    for (const char* testcase : goodTestcases) {
+        REPORTER_ASSERT(r, valid_utf8(testcase, strlen(testcase)));
+    }
+    const char* badTestcases[] = {
+        INVALID_BYTE,
+        INVALID_BYTE CONTINUATION_BYTE,
+        INVALID_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        INVALID_BYTE CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        LEADING_TWO_BYTE,
+        CONTINUATION_BYTE,
+        CONTINUATION_BYTE CONTINUATION_BYTE,
+        LEADING_THREE_BYTE CONTINUATION_BYTE,
+        CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        LEADING_FOUR_BYTE CONTINUATION_BYTE,
+        CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+
+        ASCII_BYTE INVALID_BYTE,
+        ASCII_BYTE INVALID_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE INVALID_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE INVALID_BYTE CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE LEADING_TWO_BYTE,
+        ASCII_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE LEADING_THREE_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE LEADING_FOUR_BYTE CONTINUATION_BYTE,
+        ASCII_BYTE CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE CONTINUATION_BYTE,
+
+        // LEADING_FOUR_BYTE LEADING_TWO_BYTE CONTINUATION_BYTE,
+    };
+    for (const char* testcase : badTestcases) {
+        REPORTER_ASSERT(r, !valid_utf8(testcase, strlen(testcase)));
+    }
+
+}
diff --git a/tests/Writer32Test.cpp b/tests/Writer32Test.cpp
index 3eab94c..6cb79f8 100644
--- a/tests/Writer32Test.cpp
+++ b/tests/Writer32Test.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAutoMalloc.h"
 #include "SkRandom.h"
 #include "SkReader32.h"
 #include "SkWriter32.h"
diff --git a/tests/YUVTest.cpp b/tests/YUVTest.cpp
index 913997b..0c31c09 100644
--- a/tests/YUVTest.cpp
+++ b/tests/YUVTest.cpp
@@ -5,8 +5,9 @@
  * found in the LICENSE file.
  */
 
-#include "SkCodec.h"
 #include "Resources.h"
+#include "SkAutoMalloc.h"
+#include "SkCodec.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "SkYUVSizeInfo.h"
diff --git a/third_party/angle2/BUILD.gn b/third_party/angle2/BUILD.gn
index cc45284..b6bec35 100644
--- a/third_party/angle2/BUILD.gn
+++ b/third_party/angle2/BUILD.gn
@@ -82,22 +82,17 @@
     ":commit_id",
   ]
   libs = []
-  sources =
-      rebase_path(
+  sources = rebase_path(
           compiler_gypi.angle_preprocessor_sources +
-              compiler_gypi.angle_translator_lib_sources +
-              compiler_gypi.angle_translator_lib_essl_sources +
-              compiler_gypi.angle_translator_lib_glsl_sources +
-              compiler_gypi.angle_translator_lib_hlsl_sources +
+              compiler_gypi.angle_translator_sources +
+              compiler_gypi.angle_translator_essl_sources +
+              compiler_gypi.angle_translator_glsl_sources +
+              compiler_gypi.angle_translator_hlsl_sources +
               gles_gypi.libangle_sources + gles_gypi.libangle_common_sources +
               gles_gypi.libangle_image_util_sources +
               gles_gypi.libglesv2_sources + gles_gypi.libangle_gl_sources,
           ".",
-          "$angle_root/src") +
-      [
-        "$angle_root/src/compiler/translator/ShaderLang.cpp",
-        "$angle_root/src/compiler/translator/ShaderVars.cpp",
-      ]
+          "$angle_root/src")
   if (!is_win) {
     sources -= [ "$angle_root/src/libGLESv2/libGLESv2.def" ]
   }
diff --git a/third_party/freetype/include/freetype-android/ftoption.h b/third_party/freetype/include/freetype-android/ftoption.h
index 42aa623..03fd844 100644
--- a/third_party/freetype/include/freetype-android/ftoption.h
+++ b/third_party/freetype/include/freetype-android/ftoption.h
@@ -4,7 +4,7 @@
 /*                                                                         */
 /*    User-selectable configuration macros (specification only).           */
 /*                                                                         */
-/*  Copyright 1996-2016 by                                                 */
+/*  Copyright 1996-2017 by                                                 */
 /*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
 /*                                                                         */
 /*  This file is part of the FreeType project, and may only be used,       */
@@ -82,8 +82,8 @@
   /* to control the various font drivers and modules.  The controllable    */
   /* properties are listed in the section `Controlling FreeType Modules'   */
   /* in the reference's table of contents; currently there are properties  */
-  /* for the auto-hinter (file `ftautoh.h'), CFF (file `ftcffdrv.h'), and  */
-  /* TrueType (file `ftttdrv.h').                                          */
+  /* for the auto-hinter (file `ftautoh.h'), CFF (file `ftcffdrv.h'),      */
+  /* TrueType (file `ftttdrv.h'), and PCF (file `ftpcfdrv.h').             */
   /*                                                                       */
   /* `FREETYPE_PROPERTIES' has the following syntax form (broken here into */
   /* multiple lines for better readability).                               */
@@ -835,6 +835,33 @@
   /*************************************************************************/
   /*************************************************************************/
   /****                                                                 ****/
+  /****         P C F   D R I V E R    C O N F I G U R A T I O N        ****/
+  /****                                                                 ****/
+  /*************************************************************************/
+  /*************************************************************************/
+
+
+  /*************************************************************************/
+  /*                                                                       */
+  /* There are many PCF fonts just called `Fixed' which look completely    */
+  /* different, and which have nothing to do with each other.  When        */
+  /* selecting `Fixed' in KDE or Gnome one gets results that appear rather */
+  /* random, the style changes often if one changes the size and one       */
+  /* cannot select some fonts at all.  This option makes the PCF module    */
+  /* prepend the foundry name (plus a space) to the family name.           */
+  /*                                                                       */
+  /* We also check whether we have `wide' characters; all put together, we */
+  /* get family names like `Sony Fixed' or `Misc Fixed Wide'.              */
+  /*                                                                       */
+  /* If this option is activated, it can be controlled with the            */
+  /* `no-long-family-names' property of the pcf driver module.             */
+  /*                                                                       */
+#define PCF_CONFIG_OPTION_LONG_FAMILY_NAMES
+
+
+  /*************************************************************************/
+  /*************************************************************************/
+  /****                                                                 ****/
   /****    A U T O F I T   M O D U L E    C O N F I G U R A T I O N     ****/
   /****                                                                 ****/
   /*************************************************************************/
diff --git a/third_party/gif/SkGifImageReader.cpp b/third_party/gif/SkGifImageReader.cpp
index ae5663f..2603807 100644
--- a/third_party/gif/SkGifImageReader.cpp
+++ b/third_party/gif/SkGifImageReader.cpp
@@ -312,17 +312,15 @@
         return nullptr;
 
     const PackColorProc proc = choose_pack_color_proc(false, colorType);
-    if (m_table) {
-        if (transparentPixel > (unsigned) m_table->count()
-                || m_table->operator[](transparentPixel) == SK_ColorTRANSPARENT) {
-            if (proc == m_packColorProc) {
-                // This SkColorTable has already been built with the same transparent color and
-                // packing proc. Reuse it.
-                return m_table;
-            }
-        }
+    if (m_table && proc == m_packColorProc && m_transPixel == transparentPixel) {
+        SkASSERT(transparentPixel > (unsigned) m_table->count()
+                || m_table->operator[](transparentPixel) == SK_ColorTRANSPARENT);
+        // This SkColorTable has already been built with the same transparent color and
+        // packing proc. Reuse it.
+        return m_table;
     }
     m_packColorProc = proc;
+    m_transPixel = transparentPixel;
 
     const size_t bytes = m_colors * SK_BYTES_PER_COLORMAP_ENTRY;
     sk_sp<SkData> rawData(streamBuffer->getDataAtPosition(m_position, bytes));
@@ -458,7 +456,10 @@
         }
         case SkGIFLZWStart: {
             SkASSERT(!m_frames.empty());
-            m_frames.back()->setDataSize(this->getOneByte());
+            auto* currentFrame = m_frames.back().get();
+            setRequiredFrame(currentFrame);
+
+            currentFrame->setDataSize(this->getOneByte());
             GETN(1, SkGIFSubBlock);
             break;
         }
@@ -755,33 +756,7 @@
             // The three low-order bits of currentComponent[8] specify the bits per pixel.
             const size_t numColors = 2 << (currentComponent[8] & 0x7);
             if (currentFrameIsFirstFrame()) {
-                bool hasTransparentPixel;
-                if (m_frames.size() == 0) {
-                    // We did not see a Graphics Control Extension, so no transparent
-                    // pixel was specified. But if there is no color table, this frame is
-                    // still transparent.
-                    hasTransparentPixel = !isLocalColormapDefined
-                                          && m_globalColorMap.numColors() == 0;
-                } else {
-                    // This means we did see a Graphics Control Extension, which specifies
-                    // the transparent pixel
-                    const size_t transparentPixel = m_frames[0]->transparentPixel();
-                    if (isLocalColormapDefined) {
-                        hasTransparentPixel = transparentPixel < numColors;
-                    } else {
-                        const size_t globalColors = m_globalColorMap.numColors();
-                        if (!globalColors) {
-                            // No color table for this frame, so the frame is empty.
-                            // This is technically different from having a transparent
-                            // pixel, but we'll treat it the same - nothing to draw here.
-                            hasTransparentPixel = true;
-                        } else {
-                            hasTransparentPixel = transparentPixel < globalColors;
-                        }
-                    }
-                }
-
-                if (hasTransparentPixel) {
+                if (hasTransparentPixel(0, isLocalColormapDefined, numColors)) {
                     m_firstFrameHasAlpha = true;
                     m_firstFrameSupportsIndex8 = true;
                 } else {
@@ -874,44 +849,97 @@
     return true;
 }
 
+bool SkGifImageReader::hasTransparentPixel(size_t i, bool isLocalColormapDefined,
+                                           size_t localColors) {
+    if (m_frames.size() <= i) {
+        // This should only happen when parsing the first frame.
+        SkASSERT(0 == i);
+
+        // We did not see a Graphics Control Extension, so no transparent
+        // pixel was specified. But if there is no color table, this frame is
+        // still transparent.
+        return !isLocalColormapDefined && m_globalColorMap.numColors() == 0;
+    }
+
+    const size_t transparentPixel = m_frames[i]->transparentPixel();
+    if (isLocalColormapDefined) {
+        return transparentPixel < localColors;
+    }
+
+    const size_t globalColors = m_globalColorMap.numColors();
+    if (!globalColors) {
+        // No color table for this frame, so the frame is empty.
+        // This is technically different from having a transparent
+        // pixel, but we'll treat it the same - nothing to draw here.
+        return true;
+    }
+
+    // If there is a global color table, it will be parsed before reaching
+    // here. If its numColors is set, it will be defined.
+    SkASSERT(m_globalColorMap.isDefined());
+    return transparentPixel < globalColors;
+}
+
 void SkGifImageReader::addFrameIfNecessary()
 {
     if (m_frames.empty() || m_frames.back()->isComplete()) {
         const size_t i = m_frames.size();
         std::unique_ptr<SkGIFFrameContext> frame(new SkGIFFrameContext(i));
-        if (0 == i) {
-            frame->setRequiredFrame(SkCodec::kNone);
-        } else {
-            // FIXME: We could correct these after decoding (i.e. some frames may turn out to be
-            // independent although we did not determine that here).
-            const SkGIFFrameContext* prevFrameContext = m_frames[i - 1].get();
-            switch (prevFrameContext->getDisposalMethod()) {
-                case SkCodecAnimation::Keep_DisposalMethod:
-                    frame->setRequiredFrame(i - 1);
-                    break;
-                case SkCodecAnimation::RestorePrevious_DisposalMethod:
-                    frame->setRequiredFrame(prevFrameContext->getRequiredFrame());
-                    break;
-                case SkCodecAnimation::RestoreBGColor_DisposalMethod:
-                    // If the prior frame covers the whole image
-                    if (prevFrameContext->frameRect() == SkIRect::MakeWH(m_screenWidth,
-                                                                         m_screenHeight)
-                            // Or the prior frame was independent
-                            || prevFrameContext->getRequiredFrame() == SkCodec::kNone)
-                    {
-                        // This frame is independent, since we clear everything
-                        // prior frame to the BG color
-                        frame->setRequiredFrame(SkCodec::kNone);
-                    } else {
-                        frame->setRequiredFrame(i - 1);
-                    }
-                    break;
-            }
-        }
         m_frames.push_back(std::move(frame));
     }
 }
 
+void SkGifImageReader::setRequiredFrame(SkGIFFrameContext* frame) {
+    const size_t i = frame->frameId();
+    if (0 == i) {
+        frame->setRequiredFrame(SkCodec::kNone);
+        return;
+    }
+
+    const SkGIFFrameContext* prevFrame = m_frames[i - 1].get();
+    if (prevFrame->getDisposalMethod() == SkCodecAnimation::RestorePrevious_DisposalMethod) {
+        frame->setRequiredFrame(prevFrame->getRequiredFrame());
+        return;
+    }
+
+    // Note: We could correct these after decoding - i.e. some frames may turn out to be
+    // independent if they do not use the transparent pixel, but that would require
+    // checking whether each pixel used the transparent pixel.
+    const SkGIFColorMap& localMap = frame->localColorMap();
+    const bool transValid = hasTransparentPixel(i, localMap.isDefined(), localMap.numColors());
+
+    const SkIRect prevFrameRect = prevFrame->frameRect();
+    const bool frameCoversPriorFrame = frame->frameRect().contains(prevFrameRect);
+
+    if (!transValid && frameCoversPriorFrame) {
+        frame->setRequiredFrame(prevFrame->getRequiredFrame());
+        return;
+    }
+
+    switch (prevFrame->getDisposalMethod()) {
+        case SkCodecAnimation::Keep_DisposalMethod:
+            frame->setRequiredFrame(i - 1);
+            break;
+        case SkCodecAnimation::RestorePrevious_DisposalMethod:
+            // This was already handled above.
+            SkASSERT(false);
+            break;
+        case SkCodecAnimation::RestoreBGColor_DisposalMethod:
+            // If the prior frame covers the whole image
+            if (prevFrameRect == SkIRect::MakeWH(m_screenWidth, m_screenHeight)
+                    // Or the prior frame was independent
+                    || prevFrame->getRequiredFrame() == SkCodec::kNone)
+            {
+                // This frame is independent, since we clear everything in the
+                // prior frame to the BG color
+                frame->setRequiredFrame(SkCodec::kNone);
+            } else {
+                frame->setRequiredFrame(i - 1);
+            }
+            break;
+    }
+}
+
 // FIXME: Move this method to close to doLZW().
 bool SkGIFLZWContext::prepareToDecode()
 {
diff --git a/third_party/gif/SkGifImageReader.h b/third_party/gif/SkGifImageReader.h
index ddfd2ce..c51f4a8 100644
--- a/third_party/gif/SkGifImageReader.h
+++ b/third_party/gif/SkGifImageReader.h
@@ -148,10 +148,13 @@
 
 class SkGIFColorMap final {
 public:
+    static constexpr size_t kNotFound = static_cast<size_t>(-1);
+
     SkGIFColorMap()
         : m_isDefined(false)
         , m_position(0)
         , m_colors(0)
+        , m_transPixel(kNotFound)
         , m_packColorProc(nullptr)
     {
     }
@@ -182,6 +185,8 @@
     bool m_isDefined;
     size_t m_position;
     size_t m_colors;
+    // Cached values. If these match on a new request, we can reuse m_table.
+    mutable size_t m_transPixel;
     mutable PackColorProc m_packColorProc;
     mutable sk_sp<SkColorTable> m_table;
 };
@@ -195,7 +200,7 @@
         , m_yOffset(0)
         , m_width(0)
         , m_height(0)
-        , m_transparentPixel(kNotFound)
+        , m_transparentPixel(SkGIFColorMap::kNotFound)
         , m_disposalMethod(SkCodecAnimation::Keep_DisposalMethod)
         , m_requiredFrame(SkCodec::kNone)
         , m_dataSize(0)
@@ -209,8 +214,6 @@
     {
     }
 
-    static constexpr size_t kNotFound = static_cast<size_t>(-1);
-
     ~SkGIFFrameContext()
     {
     }
@@ -388,6 +391,11 @@
     }
 
     void addFrameIfNecessary();
+    // Must be called *after* the SkGIFFrameContext's color table (if any) has been parsed.
+    void setRequiredFrame(SkGIFFrameContext*);
+    // This method is sometimes called before creating a SkGIFFrameContext, so it cannot rely
+    // on SkGIFFrameContext::localColorMap().
+    bool hasTransparentPixel(size_t frameIndex, bool hasLocalColorMap, size_t localMapColors);
     bool currentFrameIsFirstFrame() const
     {
         return m_frames.empty() || (m_frames.size() == 1u && !m_frames[0]->isComplete());
diff --git a/third_party/ktx/ktx.cpp b/third_party/ktx/ktx.cpp
index ba3ba1f..a65c6f2 100644
--- a/third_party/ktx/ktx.cpp
+++ b/third_party/ktx/ktx.cpp
@@ -6,9 +6,11 @@
  */
 
 #include "ktx.h"
+
+#include "SkAutoMalloc.h"
 #include "SkBitmap.h"
-#include "SkStream.h"
 #include "SkEndian.h"
+#include "SkStream.h"
 
 #include "gl/GrGLDefines.h"
 #include "GrConfig.h"
diff --git a/include/core/SkTRegistry.h b/tools/Registry.h
similarity index 64%
rename from include/core/SkTRegistry.h
rename to tools/Registry.h
index 0994c99..6a1e24a 100644
--- a/include/core/SkTRegistry.h
+++ b/tools/Registry.h
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2009 The Android Open Source Project
  *
@@ -6,25 +5,26 @@
  * found in the LICENSE file.
  */
 
-
-#ifndef SkTRegistry_DEFINED
-#define SkTRegistry_DEFINED
+#ifndef sk_tools_Registry_DEFINED
+#define sk_tools_Registry_DEFINED
 
 #include "SkTypes.h"
 
+namespace sk_tools {
+
 /** Template class that registers itself (in the constructor) into a linked-list
     and provides a function-pointer. This can be used to auto-register a set of
     services, e.g. a set of image codecs.
  */
-template <typename T> class SkTRegistry : SkNoncopyable {
+template <typename T> class Registry : SkNoncopyable {
 public:
     typedef T Factory;
 
-    explicit SkTRegistry(T fact) : fFact(fact) {
+    explicit Registry(T fact) : fFact(fact) {
 #ifdef SK_BUILD_FOR_ANDROID
         // work-around for double-initialization bug
         {
-            SkTRegistry* reg = gHead;
+            Registry* reg = gHead;
             while (reg) {
                 if (reg == this) {
                     return;
@@ -37,19 +37,21 @@
         gHead  = this;
     }
 
-    static const SkTRegistry* Head() { return gHead; }
+    static const Registry* Head() { return gHead; }
 
-    const SkTRegistry* next() const { return fChain; }
+    const Registry* next() const { return fChain; }
     const Factory& factory() const { return fFact; }
 
 private:
-    Factory      fFact;
-    SkTRegistry* fChain;
+    Factory   fFact;
+    Registry* fChain;
 
-    static SkTRegistry* gHead;
+    static Registry* gHead;
 };
 
 // The caller still needs to declare an instance of this somewhere
-template <typename T> SkTRegistry<T>* SkTRegistry<T>::gHead;
+template <typename T> Registry<T>* Registry<T>::gHead;
+
+}  // namespace sk_tools
 
 #endif
diff --git a/tools/SkShaper_harfbuzz.cpp b/tools/SkShaper_harfbuzz.cpp
index 2c4d211..9c7d69e 100644
--- a/tools/SkShaper_harfbuzz.cpp
+++ b/tools/SkShaper_harfbuzz.cpp
@@ -28,10 +28,9 @@
                                   [](void* p) { delete (SkStreamAsset*)p; }));
     } else {
         // SkDebugf("Extra SkStreamAsset copy\n");
-        SkAutoMalloc autoMalloc(size);
-        asset->read(autoMalloc.get(), size);
-        void* ptr = autoMalloc.get();
-        blob.reset(hb_blob_create((char*)autoMalloc.release(), SkToUInt(size),
+        void* ptr = size ? sk_malloc_throw(size) : nullptr;
+        asset->read(ptr, size);
+        blob.reset(hb_blob_create((char*)ptr, SkToUInt(size),
                                   HB_MEMORY_MODE_READONLY, ptr, sk_free));
     }
     SkASSERT(blob);
diff --git a/tools/debugger/SkDrawCommand.cpp b/tools/debugger/SkDrawCommand.cpp
index 4eaf2ea..6dea988 100644
--- a/tools/debugger/SkDrawCommand.cpp
+++ b/tools/debugger/SkDrawCommand.cpp
@@ -9,6 +9,7 @@
 
 #include "png.h"
 
+#include "SkAutoMalloc.h"
 #include "SkBlurMaskFilter.h"
 #include "SkColorFilter.h"
 #include "SkDashPathEffect.h"
@@ -704,7 +705,7 @@
 bool SkDrawCommand::flatten(const SkImage& image, Json::Value* target,
                             UrlDataManager& urlDataManager) {
     size_t rowBytes = 4 * image.width();
-    SkAutoFree buffer(sk_malloc_throw(rowBytes * image.height()));
+    SkAutoMalloc buffer(rowBytes * image.height());
     SkImageInfo dstInfo = SkImageInfo::Make(image.width(), image.height(),
                                             kN32_SkColorType, kPremul_SkAlphaType);
     if (!image.readPixels(dstInfo, buffer.get(), rowBytes, 0, 0)) {
diff --git a/tools/flags/SkCommonFlagsConfig.cpp b/tools/flags/SkCommonFlagsConfig.cpp
index 5763e4a..6dcb2f1 100644
--- a/tools/flags/SkCommonFlagsConfig.cpp
+++ b/tools/flags/SkCommonFlagsConfig.cpp
@@ -20,9 +20,6 @@
 #if defined(SK_BUILD_FOR_WIN)
     " angle_d3d11_es2"
 #endif
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    " hwui"
-#endif
     ;
 
 static const struct {
@@ -61,9 +58,11 @@
     { "debug",                 "gpu", "api=debug" },
     { "nullgpu",               "gpu", "api=null" },
     { "angle_d3d11_es2",       "gpu", "api=angle_d3d11_es2" },
+    { "angle_d3d11_es3",       "gpu", "api=angle_d3d11_es3" },
     { "angle_d3d9_es2",        "gpu", "api=angle_d3d9_es2" },
     { "angle_d3d11_es2_msaa4", "gpu", "api=angle_d3d11_es2,samples=4" },
     { "angle_gl_es2",          "gpu", "api=angle_gl_es2" },
+    { "angle_gl_es3",          "gpu", "api=angle_gl_es3" },
     { "commandbuffer",         "gpu", "api=commandbuffer" }
 #if SK_MESA
     ,{ "mesa",                 "gpu", "api=mesa" }
@@ -82,11 +81,7 @@
 };
 
 static const char configHelp[] =
-    "Options: 565 8888 srgb f16 nonrendering null pdf pdfa skp pipe svg xps"
-#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    " hwui"
-#endif
-    ;
+    "Options: 565 8888 srgb f16 nonrendering null pdf pdfa skp pipe svg xps";
 
 static const char* config_help_fn() {
     static SkString helpString;
diff --git a/tools/gpu/GrContextFactory.cpp b/tools/gpu/GrContextFactory.cpp
index 8d9bd41..bd745bc 100644
--- a/tools/gpu/GrContextFactory.cpp
+++ b/tools/gpu/GrContextFactory.cpp
@@ -24,6 +24,20 @@
 #include "gl/GrGLGpu.h"
 #include "GrCaps.h"
 
+#if defined(SK_BUILD_FOR_WIN32) && defined(SK_ENABLE_DISCRETE_GPU)
+extern "C" {
+    // NVIDIA documents that the presence and value of this symbol programmatically enable the high
+    // performance GPU in laptops with switchable graphics.
+    //   https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm
+    // From testing, including this symbol, even if it is set to 0, we still get the NVIDIA GPU.
+    _declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
+
+    // AMD has a similar mechanism, although I don't have an AMD laptop, so this is untested.
+    //   https://community.amd.com/thread/169965
+    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
+}
+#endif
+
 namespace sk_gpu_test {
 GrContextFactory::GrContextFactory() { }
 
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index 79891f4..b1c179b 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -221,15 +221,30 @@
 
 void GrResourceCache::changeTimestamp(uint32_t newTimestamp) { fTimestamp = newTimestamp; }
 
+#ifdef SK_DEBUG
+int GrResourceCache::countUniqueKeysWithTag(const char* tag) const {
+    int count = 0;
+    UniqueHash::ConstIter iter(&fUniqueHash);
+    while (!iter.done()) {
+        if (0 == strcmp(tag, (*iter).getUniqueKey().tag())) {
+            ++count;
+        }
+        ++iter;
+    }
+    return count;
+}
+#endif
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #define ASSERT_SINGLE_OWNER \
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fRenderTargetContext->fSingleOwner);)
 #define RETURN_IF_ABANDONED        if (fRenderTargetContext->fDrawingManager->wasAbandoned()) { return; }
 
-void GrRenderTargetContextPriv::testingOnly_addDrawOp(const GrPaint& paint,
+void GrRenderTargetContextPriv::testingOnly_addDrawOp(GrPaint&& paint,
                                                       GrAAType aaType,
-                                                      sk_sp<GrDrawOp> op,
+                                                      std::unique_ptr<GrDrawOp>
+                                                              op,
                                                       const GrUserStencilSettings* uss,
                                                       bool snapToCenters) {
     ASSERT_SINGLE_OWNER
@@ -238,7 +253,7 @@
     GR_AUDIT_TRAIL_AUTO_FRAME(fRenderTargetContext->fAuditTrail,
                               "GrRenderTargetContext::testingOnly_addDrawOp");
 
-    GrPipelineBuilder pipelineBuilder(paint, aaType);
+    GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
     if (uss) {
         pipelineBuilder.setUserStencil(uss);
     }
@@ -247,7 +262,7 @@
     }
 
     fRenderTargetContext->getOpList()->addDrawOp(pipelineBuilder, fRenderTargetContext, GrNoClip(),
-                                                 op);
+                                                 std::move(op));
 }
 
 #undef ASSERT_SINGLE_OWNER
diff --git a/tools/monobench.cpp b/tools/monobench.cpp
index d5c9611..fa0de8a 100644
--- a/tools/monobench.cpp
+++ b/tools/monobench.cpp
@@ -94,6 +94,7 @@
 
     int samples = 0;
     while (samples < limit) {
+        std::random_shuffle(benches.begin(), benches.end());
         for (auto& bench : benches) {
             for (int loops = 1; loops < 1000000000;) {
                 bench.b->preDraw(nullptr);
@@ -110,15 +111,22 @@
                 bench.best = std::min(bench.best, elapsed / loops);
                 samples++;
 
-                std::sort(benches.begin(), benches.end(), [](const Bench& a, const Bench& b) {
+                struct Result { const char* name; ns best; };
+                std::vector<Result> sorted(benches.size());
+                for (size_t i = 0; i < benches.size(); i++) {
+                    sorted[i].name = benches[i].name.c_str();
+                    sorted[i].best = benches[i].best;
+                }
+                std::sort(sorted.begin(), sorted.end(), [](const Result& a, const Result& b) {
                     return a.best < b.best;
                 });
+
                 SkDebugf("%s%d", kSkOverwriteLine, samples);
-                for (auto& bench : benches) {
-                    if (benches.size() == 1) {
-                        SkDebugf("  %s %gns" , bench.name.c_str(), bench.best.count());
+                for (auto& result : sorted) {
+                    if (sorted.size() == 1) {
+                        SkDebugf("  %s %gns" , result.name, result.best.count());
                     } else {
-                        SkDebugf("  %s %.3gx", bench.name.c_str(), bench.best / benches[0].best);
+                        SkDebugf("  %s %.3gx", result.name, result.best / sorted[0].best);
                     }
                 }
                 break;
diff --git a/tools/skiaserve/Request.cpp b/tools/skiaserve/Request.cpp
index 0e55d92..ff8849a 100644
--- a/tools/skiaserve/Request.cpp
+++ b/tools/skiaserve/Request.cpp
@@ -259,7 +259,7 @@
     SkCanvas* canvas = this->getCanvas();
     Json::Value root = fDebugCanvas->toJSON(fUrlDataManager, n, canvas);
     root["mode"] = Json::Value(fGPUEnabled ? "gpu" : "cpu");
-    root["drawGpuBatchBounds"] = Json::Value(fDebugCanvas->getDrawGpuOpBounds());
+    root["drawGpuOpBounds"] = Json::Value(fDebugCanvas->getDrawGpuOpBounds());
     root["colorMode"] = Json::Value(fColorMode);
     SkDynamicMemoryWStream stream;
     stream.writeText(Json::FastWriter().write(root).c_str());
diff --git a/tools/skiaserve/urlhandlers/OpBoundsHandler.cpp b/tools/skiaserve/urlhandlers/OpBoundsHandler.cpp
index f0e9758..e9a120f 100644
--- a/tools/skiaserve/urlhandlers/OpBoundsHandler.cpp
+++ b/tools/skiaserve/urlhandlers/OpBoundsHandler.cpp
@@ -14,7 +14,7 @@
 using namespace Response;
 
 bool OpBoundsHandler::canHandle(const char* method, const char* url) {
-    static const char* kBasePath = "/batchBounds/";
+    static const char* kBasePath = "/gpuOpBounds/";
     return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
            0 == strncmp(url, kBasePath, strlen(kBasePath));
 }
diff --git a/tools/skiaserve/urlhandlers/OpsHandler.cpp b/tools/skiaserve/urlhandlers/OpsHandler.cpp
index a44e594..852c526 100644
--- a/tools/skiaserve/urlhandlers/OpsHandler.cpp
+++ b/tools/skiaserve/urlhandlers/OpsHandler.cpp
@@ -14,7 +14,7 @@
 using namespace Response;
 
 bool OpsHandler::canHandle(const char* method, const char* url) {
-    const char* kBasePath = "/batches";
+    const char* kBasePath = "/ops";
     return 0 == strncmp(url, kBasePath, strlen(kBasePath));
 }
 
@@ -27,7 +27,7 @@
         return MHD_NO;
     }
 
-    // /batches
+    // /ops
     if (0 == strcmp(method, MHD_HTTP_METHOD_GET)) {
         int n = request->getLastOp();
 
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index c1d08d1..49a331b 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -150,10 +150,6 @@
     fBackendType = get_backend_type(FLAGS_backend[0]);
     fWindow = Window::CreateNativeWindow(platformData);
     fWindow->attach(fBackendType, DisplayParams());
-#if defined(SK_VULKAN) && defined(SK_BUILD_FOR_UNIX)
-    // Vulkan doesn't seem to handle a single refresh properly on Linux
-    fRefresh = (sk_app::Window::kVulkan_BackendType == fBackendType);
-#endif
 
     // register callbacks
     fCommands.attach(fWindow);
@@ -198,8 +194,8 @@
         this->changeZoomLevel(-1.f / 32.f);
         fWindow->inval();
     });
-#if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC)
     fCommands.addCommand('d', "Modes", "Change rendering backend", [this]() {
+#if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC)
         if (sk_app::Window::kRaster_BackendType == fBackendType) {
             fBackendType = sk_app::Window::kNativeGL_BackendType;
 #ifdef SK_VULKAN
@@ -209,14 +205,19 @@
         } else {
             fBackendType = sk_app::Window::kRaster_BackendType;
         }
-
+#elif defined(SK_BUILD_FOR_UNIX)
+        // Switching to and from Vulkan is problematic on Linux so disabled for now
+        if (sk_app::Window::kRaster_BackendType == fBackendType) {
+            fBackendType = sk_app::Window::kNativeGL_BackendType;
+        } else if (sk_app::Window::kNativeGL_BackendType == fBackendType) {
+            fBackendType = sk_app::Window::kRaster_BackendType;
+        }
+#endif
         fWindow->detach();
 
-#ifdef SK_VULKAN
-        // Switching from OpenGL to Vulkan in the same window is problematic at this point,
-        // so we just delete the window and recreate it.
-        // On Windows, only tearing down the window when going from OpenGL to Vulkan works fine.
-        // On Linux, we may need to tear down the window for the Vulkan to OpenGL case as well.
+#if defined(SK_BUILD_FOR_WIN) && defined(SK_VULKAN)
+        // Switching from OpenGL to Vulkan in the same window is problematic at this point on
+        // Windows, so we just delete the window and recreate it.
         if (sk_app::Window::kVulkan_BackendType == fBackendType) {
             delete fWindow;
             fWindow = Window::CreateNativeWindow(nullptr);
@@ -229,16 +230,11 @@
         }
 #endif
         fWindow->attach(fBackendType, DisplayParams());
-#if defined(SK_VULKAN) && defined(SK_BUILD_FOR_UNIX)
-        // Vulkan doesn't seem to handle a single refresh properly on Linux
-        fRefresh = (sk_app::Window::kVulkan_BackendType == fBackendType);
-#endif
 
         this->updateTitle();
         fWindow->inval();
         fWindow->show();
     });
-#endif
 
     // set up slides
     this->initSlides();
diff --git a/tools/viewer/sk_app/VulkanWindowContext.cpp b/tools/viewer/sk_app/VulkanWindowContext.cpp
index 9d1e043..4802e21 100644
--- a/tools/viewer/sk_app/VulkanWindowContext.cpp
+++ b/tools/viewer/sk_app/VulkanWindowContext.cpp
@@ -8,6 +8,7 @@
 
 #include "GrContext.h"
 #include "GrRenderTarget.h"
+#include "SkAutoMalloc.h"
 #include "SkSurface.h"
 #include "VulkanWindowContext.h"
 
@@ -446,6 +447,9 @@
         if (!this->createSwapchain(-1, -1, fDisplayParams)) {
             return nullptr;
         }
+        backbuffer = this->getAvailableBackbuffer();
+        GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                            ResetFences(fBackendContext->fDevice, 2, backbuffer->fUsageFences));
 
         // acquire the image
         res = fAcquireNextImageKHR(fBackendContext->fDevice, fSwapchain, UINT64_MAX,
diff --git a/tools/viewer/sk_app/unix/Window_unix.cpp b/tools/viewer/sk_app/unix/Window_unix.cpp
index 3520257..d22502b 100644
--- a/tools/viewer/sk_app/unix/Window_unix.cpp
+++ b/tools/viewer/sk_app/unix/Window_unix.cpp
@@ -26,6 +26,7 @@
 
 Window* Window::CreateNativeWindow(void* platformData) {
     Display* display = (Display*)platformData;
+    SkASSERT(display);
 
     Window_unix* window = new Window_unix();
     if (!window->initWindow(display, nullptr)) {
diff --git a/tools/viewer/sk_app/unix/main_unix.cpp b/tools/viewer/sk_app/unix/main_unix.cpp
index c0fd418..a5e7004 100644
--- a/tools/viewer/sk_app/unix/main_unix.cpp
+++ b/tools/viewer/sk_app/unix/main_unix.cpp
@@ -46,16 +46,18 @@
             (void) select(count, &in_fds, NULL, NULL, &tv);
         }
 
-        // Handle XEvents (if any) and flush the input 
+        // Handle XEvents (if any) and flush the input
         int count = XPending(display);
         while (count-- && !done) {
             XEvent event;
             XNextEvent(display, &event);
 
             sk_app::Window_unix* win = sk_app::Window_unix::gWindowMap.find(event.xany.window);
+            SkASSERT(win);
+
             // paint and resize events get collapsed
             switch (event.type) {
-            case Expose: 
+            case Expose:
                 win->markPendingPaint();
                 pendingWindows.add(win);
                 break;
diff --git a/tools/viewer/sk_app/win/RasterWindowContext_win.cpp b/tools/viewer/sk_app/win/RasterWindowContext_win.cpp
index ca738db..96fe8f2 100644
--- a/tools/viewer/sk_app/win/RasterWindowContext_win.cpp
+++ b/tools/viewer/sk_app/win/RasterWindowContext_win.cpp
@@ -5,9 +5,10 @@
  * found in the LICENSE file.
  */
 
-#include "WindowContextFactory_win.h"
 #include "../RasterWindowContext.h"
+#include "SkAutoMalloc.h"
 #include "SkSurface.h"
+#include "WindowContextFactory_win.h"
 
 #include <Windows.h>
 
diff --git a/whitespace.txt b/whitespace.txt
index e42078e..202d72f 100644
--- a/whitespace.txt
+++ b/whitespace.txt
@@ -6,6 +6,6 @@
 This file has gotten so boring.
 epoger added this line using a pure git checkout
 florin was here
-more whitespace
+more  whitespace
 all the things!!!
 Driver  update.